diff --git a/.git_backup/COMMIT_EDITMSG b/.git_backup/COMMIT_EDITMSG deleted file mode 100644 index 40c93ea..0000000 --- a/.git_backup/COMMIT_EDITMSG +++ /dev/null @@ -1 +0,0 @@ -fix: resolve satellite NORAD ID lookup to fix propagation loop diff --git a/.git_backup/FETCH_HEAD b/.git_backup/FETCH_HEAD deleted file mode 100644 index 9ccf0ac..0000000 --- a/.git_backup/FETCH_HEAD +++ /dev/null @@ -1 +0,0 @@ -313aa32a9b08c1ddce4e9c801bdda210e136d67f branch 'main' of https://github.com/BigBodyCobain/Shadowbroker diff --git a/.git_backup/HEAD b/.git_backup/HEAD deleted file mode 100644 index b870d82..0000000 --- a/.git_backup/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/main diff --git a/.git_backup/ORIG_HEAD b/.git_backup/ORIG_HEAD deleted file mode 100644 index 2cb741a..0000000 --- a/.git_backup/ORIG_HEAD +++ /dev/null @@ -1 +0,0 @@ -e1f4ac2cfb114de61c0d83234b8d2deb545b2301 diff --git a/.git_backup/config b/.git_backup/config deleted file mode 100644 index 6acb197..0000000 --- a/.git_backup/config +++ /dev/null @@ -1,13 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = false - bare = false - logallrefupdates = true - symlinks = false - ignorecase = true -[remote "origin"] - url = https://BigBodyCobain@github.com/BigBodyCobain/Shadowbroker.git - fetch = +refs/heads/*:refs/remotes/origin/* -[branch "main"] - remote = origin - merge = refs/heads/main diff --git a/.git_backup/description b/.git_backup/description deleted file mode 100644 index 498b267..0000000 --- a/.git_backup/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/.git_backup/gk/config b/.git_backup/gk/config deleted file mode 100644 index 1fbde9d..0000000 --- a/.git_backup/gk/config +++ /dev/null @@ -1,3 +0,0 @@ -[branch "main"] - gk-last-accessed = 2026-03-08T21:04:31.109Z - gk-last-modified = 2026-03-08T21:04:31.109Z diff --git a/.git_backup/hooks/applypatch-msg.sample b/.git_backup/hooks/applypatch-msg.sample deleted file mode 100644 index a5d7b84..0000000 --- a/.git_backup/hooks/applypatch-msg.sample +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message taken by -# applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. The hook is -# allowed to edit the commit message file. -# -# To enable this hook, rename this file to "applypatch-msg". - -. git-sh-setup -commitmsg="$(git rev-parse --git-path hooks/commit-msg)" -test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} -: diff --git a/.git_backup/hooks/commit-msg.sample b/.git_backup/hooks/commit-msg.sample deleted file mode 100644 index b58d118..0000000 --- a/.git_backup/hooks/commit-msg.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message. -# Called by "git commit" with one argument, the name of the file -# that has the commit message. The hook should exit with non-zero -# status after issuing an appropriate message if it wants to stop the -# commit. The hook is allowed to edit the commit message file. -# -# To enable this hook, rename this file to "commit-msg". - -# Uncomment the below to add a Signed-off-by line to the message. -# Doing this in a hook is a bad idea in general, but the prepare-commit-msg -# hook is more suited to it. -# -# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" - -# This example catches duplicate Signed-off-by lines. - -test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { - echo >&2 Duplicate Signed-off-by lines. - exit 1 -} diff --git a/.git_backup/hooks/fsmonitor-watchman.sample b/.git_backup/hooks/fsmonitor-watchman.sample deleted file mode 100644 index 23e856f..0000000 --- a/.git_backup/hooks/fsmonitor-watchman.sample +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use IPC::Open2; - -# An example hook script to integrate Watchman -# (https://facebook.github.io/watchman/) with git to speed up detecting -# new and modified files. -# -# The hook is passed a version (currently 2) and last update token -# formatted as a string and outputs to stdout a new update token and -# all files that have been modified since the update token. Paths must -# be relative to the root of the working tree and separated by a single NUL. -# -# To enable this hook, rename this file to "query-watchman" and set -# 'git config core.fsmonitor .git/hooks/query-watchman' -# -my ($version, $last_update_token) = @ARGV; - -# Uncomment for debugging -# print STDERR "$0 $version $last_update_token\n"; - -# Check the hook interface version -if ($version ne 2) { - die "Unsupported query-fsmonitor hook version '$version'.\n" . - "Falling back to scanning...\n"; -} - -my $git_work_tree = get_working_dir(); - -my $retry = 1; - -my $json_pkg; -eval { - require JSON::XS; - $json_pkg = "JSON::XS"; - 1; -} or do { - require JSON::PP; - $json_pkg = "JSON::PP"; -}; - -launch_watchman(); - -sub launch_watchman { - my $o = watchman_query(); - if (is_work_tree_watched($o)) { - output_result($o->{clock}, @{$o->{files}}); - } -} - -sub output_result { - my ($clockid, @files) = @_; - - # Uncomment for debugging watchman output - # open (my $fh, ">", ".git/watchman-output.out"); - # binmode $fh, ":utf8"; - # print $fh "$clockid\n@files\n"; - # close $fh; - - binmode STDOUT, ":utf8"; - print $clockid; - print "\0"; - local $, = "\0"; - print @files; -} - -sub watchman_clock { - my $response = qx/watchman clock "$git_work_tree"/; - die "Failed to get clock id on '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - - return $json_pkg->new->utf8->decode($response); -} - -sub watchman_query { - my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') - or die "open2() failed: $!\n" . - "Falling back to scanning...\n"; - - # In the query expression below we're asking for names of files that - # changed since $last_update_token but not from the .git folder. - # - # To accomplish this, we're using the "since" generator to use the - # recency index to select candidate nodes and "fields" to limit the - # output to file names only. Then we're using the "expression" term to - # further constrain the results. - my $last_update_line = ""; - if (substr($last_update_token, 0, 1) eq "c") { - $last_update_token = "\"$last_update_token\""; - $last_update_line = qq[\n"since": $last_update_token,]; - } - my $query = <<" END"; - ["query", "$git_work_tree", {$last_update_line - "fields": ["name"], - "expression": ["not", ["dirname", ".git"]] - }] - END - - # Uncomment for debugging the watchman query - # open (my $fh, ">", ".git/watchman-query.json"); - # print $fh $query; - # close $fh; - - print CHLD_IN $query; - close CHLD_IN; - my $response = do {local $/; }; - - # Uncomment for debugging the watch response - # open ($fh, ">", ".git/watchman-response.json"); - # print $fh $response; - # close $fh; - - die "Watchman: command returned no output.\n" . - "Falling back to scanning...\n" if $response eq ""; - die "Watchman: command returned invalid output: $response\n" . - "Falling back to scanning...\n" unless $response =~ /^\{/; - - return $json_pkg->new->utf8->decode($response); -} - -sub is_work_tree_watched { - my ($output) = @_; - my $error = $output->{error}; - if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { - $retry--; - my $response = qx/watchman watch "$git_work_tree"/; - die "Failed to make watchman watch '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - $output = $json_pkg->new->utf8->decode($response); - $error = $output->{error}; - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - # Uncomment for debugging watchman output - # open (my $fh, ">", ".git/watchman-output.out"); - # close $fh; - - # Watchman will always return all files on the first query so - # return the fast "everything is dirty" flag to git and do the - # Watchman query just to get it over with now so we won't pay - # the cost in git to look up each individual file. - my $o = watchman_clock(); - $error = $output->{error}; - - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - output_result($o->{clock}, ("/")); - $last_update_token = $o->{clock}; - - eval { launch_watchman() }; - return 0; - } - - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - return 1; -} - -sub get_working_dir { - my $working_dir; - if ($^O =~ 'msys' || $^O =~ 'cygwin') { - $working_dir = Win32::GetCwd(); - $working_dir =~ tr/\\/\//; - } else { - require Cwd; - $working_dir = Cwd::cwd(); - } - - return $working_dir; -} diff --git a/.git_backup/hooks/post-update.sample b/.git_backup/hooks/post-update.sample deleted file mode 100644 index ec17ec1..0000000 --- a/.git_backup/hooks/post-update.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare a packed repository for use over -# dumb transports. -# -# To enable this hook, rename this file to "post-update". - -exec git update-server-info diff --git a/.git_backup/hooks/pre-applypatch.sample b/.git_backup/hooks/pre-applypatch.sample deleted file mode 100644 index 4142082..0000000 --- a/.git_backup/hooks/pre-applypatch.sample +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed -# by applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-applypatch". - -. git-sh-setup -precommit="$(git rev-parse --git-path hooks/pre-commit)" -test -x "$precommit" && exec "$precommit" ${1+"$@"} -: diff --git a/.git_backup/hooks/pre-commit.sample b/.git_backup/hooks/pre-commit.sample deleted file mode 100644 index 29ed5ee..0000000 --- a/.git_backup/hooks/pre-commit.sample +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-commit". - -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=$(git hash-object -t tree /dev/null) -fi - -# If you want to allow non-ASCII filenames set this variable to true. -allownonascii=$(git config --type=bool hooks.allownonascii) - -# Redirect output to stderr. -exec 1>&2 - -# Cross platform projects tend to avoid non-ASCII filenames; prevent -# them from being added to the repository. We exploit the fact that the -# printable range starts at the space character and ends with tilde. -if [ "$allownonascii" != "true" ] && - # Note that the use of brackets around a tr range is ok here, (it's - # even required, for portability to Solaris 10's /usr/bin/tr), since - # the square bracket bytes happen to fall in the designated range. - test $(git diff-index --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 -then - cat <<\EOF -Error: Attempt to add a non-ASCII file name. - -This can cause problems if you want to work with people on other platforms. - -To be portable it is advisable to rename the file. - -If you know what you are doing you can disable this check using: - - git config hooks.allownonascii true -EOF - exit 1 -fi - -# If there are whitespace errors, print the offending file names and fail. -exec git diff-index --check --cached $against -- diff --git a/.git_backup/hooks/pre-merge-commit.sample b/.git_backup/hooks/pre-merge-commit.sample deleted file mode 100644 index 399eab1..0000000 --- a/.git_backup/hooks/pre-merge-commit.sample +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git merge" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message to -# stderr if it wants to stop the merge commit. -# -# To enable this hook, rename this file to "pre-merge-commit". - -. git-sh-setup -test -x "$GIT_DIR/hooks/pre-commit" && - exec "$GIT_DIR/hooks/pre-commit" -: diff --git a/.git_backup/hooks/pre-push.sample b/.git_backup/hooks/pre-push.sample deleted file mode 100644 index 4ce688d..0000000 --- a/.git_backup/hooks/pre-push.sample +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh - -# An example hook script to verify what is about to be pushed. Called by "git -# push" after it has checked the remote status, but before anything has been -# pushed. If this script exits with a non-zero status nothing will be pushed. -# -# This hook is called with the following parameters: -# -# $1 -- Name of the remote to which the push is being done -# $2 -- URL to which the push is being done -# -# If pushing without using a named remote those arguments will be equal. -# -# Information about the commits which are being pushed is supplied as lines to -# the standard input in the form: -# -# -# -# This sample shows how to prevent push of commits where the log message starts -# with "WIP" (work in progress). - -remote="$1" -url="$2" - -zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" - exit 1 - fi - fi -done - -exit 0 diff --git a/.git_backup/hooks/pre-rebase.sample b/.git_backup/hooks/pre-rebase.sample deleted file mode 100644 index 6cbef5c..0000000 --- a/.git_backup/hooks/pre-rebase.sample +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006, 2008 Junio C Hamano -# -# The "pre-rebase" hook is run just before "git rebase" starts doing -# its job, and can prevent the command from running by exiting with -# non-zero status. -# -# The hook is called with the following parameters: -# -# $1 -- the upstream the series was forked from. -# $2 -- the branch being rebased (or empty when rebasing the current branch). -# -# This sample shows how to prevent topic branches that are already -# merged to 'next' branch from getting rebased, because allowing it -# would result in rebasing already published history. - -publish=next -basebranch="$1" -if test "$#" = 2 -then - topic="refs/heads/$2" -else - topic=`git symbolic-ref HEAD` || - exit 0 ;# we do not interrupt rebasing detached HEAD -fi - -case "$topic" in -refs/heads/??/*) - ;; -*) - exit 0 ;# we do not interrupt others. - ;; -esac - -# Now we are dealing with a topic branch being rebased -# on top of master. Is it OK to rebase it? - -# Does the topic really exist? -git show-ref -q "$topic" || { - echo >&2 "No such branch $topic" - exit 1 -} - -# Is topic fully merged to master? -not_in_master=`git rev-list --pretty=oneline ^master "$topic"` -if test -z "$not_in_master" -then - echo >&2 "$topic is fully merged to master; better remove it." - exit 1 ;# we could allow it, but there is no point. -fi - -# Is topic ever merged to next? If so you should not be rebasing it. -only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` -only_next_2=`git rev-list ^master ${publish} | sort` -if test "$only_next_1" = "$only_next_2" -then - not_in_topic=`git rev-list "^$topic" master` - if test -z "$not_in_topic" - then - echo >&2 "$topic is already up to date with master" - exit 1 ;# we could allow it, but there is no point. - else - exit 0 - fi -else - not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` - /usr/bin/perl -e ' - my $topic = $ARGV[0]; - my $msg = "* $topic has commits already merged to public branch:\n"; - my (%not_in_next) = map { - /^([0-9a-f]+) /; - ($1 => 1); - } split(/\n/, $ARGV[1]); - for my $elem (map { - /^([0-9a-f]+) (.*)$/; - [$1 => $2]; - } split(/\n/, $ARGV[2])) { - if (!exists $not_in_next{$elem->[0]}) { - if ($msg) { - print STDERR $msg; - undef $msg; - } - print STDERR " $elem->[1]\n"; - } - } - ' "$topic" "$not_in_next" "$not_in_master" - exit 1 -fi - -<<\DOC_END - -This sample hook safeguards topic branches that have been -published from being rewound. - -The workflow assumed here is: - - * Once a topic branch forks from "master", "master" is never - merged into it again (either directly or indirectly). - - * Once a topic branch is fully cooked and merged into "master", - it is deleted. If you need to build on top of it to correct - earlier mistakes, a new topic branch is created by forking at - the tip of the "master". This is not strictly necessary, but - it makes it easier to keep your history simple. - - * Whenever you need to test or publish your changes to topic - branches, merge them into "next" branch. - -The script, being an example, hardcodes the publish branch name -to be "next", but it is trivial to make it configurable via -$GIT_DIR/config mechanism. - -With this workflow, you would want to know: - -(1) ... if a topic branch has ever been merged to "next". Young - topic branches can have stupid mistakes you would rather - clean up before publishing, and things that have not been - merged into other branches can be easily rebased without - affecting other people. But once it is published, you would - not want to rewind it. - -(2) ... if a topic branch has been fully merged to "master". - Then you can delete it. More importantly, you should not - build on top of it -- other people may already want to - change things related to the topic as patches against your - "master", so if you need further changes, it is better to - fork the topic (perhaps with the same name) afresh from the - tip of "master". - -Let's look at this example: - - o---o---o---o---o---o---o---o---o---o "next" - / / / / - / a---a---b A / / - / / / / - / / c---c---c---c B / - / / / \ / - / / / b---b C \ / - / / / / \ / - ---o---o---o---o---o---o---o---o---o---o---o "master" - - -A, B and C are topic branches. - - * A has one fix since it was merged up to "next". - - * B has finished. It has been fully merged up to "master" and "next", - and is ready to be deleted. - - * C has not merged to "next" at all. - -We would want to allow C to be rebased, refuse A, and encourage -B to be deleted. - -To compute (1): - - git rev-list ^master ^topic next - git rev-list ^master next - - if these match, topic has not merged in next at all. - -To compute (2): - - git rev-list master..topic - - if this is empty, it is fully merged to "master". - -DOC_END diff --git a/.git_backup/hooks/pre-receive.sample b/.git_backup/hooks/pre-receive.sample deleted file mode 100644 index a1fd29e..0000000 --- a/.git_backup/hooks/pre-receive.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to make use of push options. -# The example simply echoes all push options that start with 'echoback=' -# and rejects all pushes when the "reject" push option is used. -# -# To enable this hook, rename this file to "pre-receive". - -if test -n "$GIT_PUSH_OPTION_COUNT" -then - i=0 - while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" - do - eval "value=\$GIT_PUSH_OPTION_$i" - case "$value" in - echoback=*) - echo "echo from the pre-receive-hook: ${value#*=}" >&2 - ;; - reject) - exit 1 - esac - i=$((i + 1)) - done -fi diff --git a/.git_backup/hooks/prepare-commit-msg.sample b/.git_backup/hooks/prepare-commit-msg.sample deleted file mode 100644 index 10fa14c..0000000 --- a/.git_backup/hooks/prepare-commit-msg.sample +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare the commit log message. -# Called by "git commit" with the name of the file that has the -# commit message, followed by the description of the commit -# message's source. The hook's purpose is to edit the commit -# message file. If the hook fails with a non-zero status, -# the commit is aborted. -# -# To enable this hook, rename this file to "prepare-commit-msg". - -# This hook includes three examples. The first one removes the -# "# Please enter the commit message..." help message. -# -# The second includes the output of "git diff --name-status -r" -# into the message, just before the "git status" output. It is -# commented because it doesn't cope with --amend or with squashed -# commits. -# -# The third example adds a Signed-off-by line to the message, that can -# still be edited. This is rarely a good idea. - -COMMIT_MSG_FILE=$1 -COMMIT_SOURCE=$2 -SHA1=$3 - -/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" - -# case "$COMMIT_SOURCE,$SHA1" in -# ,|template,) -# /usr/bin/perl -i.bak -pe ' -# print "\n" . `git diff --cached --name-status -r` -# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; -# *) ;; -# esac - -# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" -# if test -z "$COMMIT_SOURCE" -# then -# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" -# fi diff --git a/.git_backup/hooks/push-to-checkout.sample b/.git_backup/hooks/push-to-checkout.sample deleted file mode 100644 index af5a0c0..0000000 --- a/.git_backup/hooks/push-to-checkout.sample +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/sh - -# An example hook script to update a checked-out tree on a git push. -# -# This hook is invoked by git-receive-pack(1) when it reacts to git -# push and updates reference(s) in its repository, and when the push -# tries to update the branch that is currently checked out and the -# receive.denyCurrentBranch configuration variable is set to -# updateInstead. -# -# By default, such a push is refused if the working tree and the index -# of the remote repository has any difference from the currently -# checked out commit; when both the working tree and the index match -# the current commit, they are updated to match the newly pushed tip -# of the branch. This hook is to be used to override the default -# behaviour; however the code below reimplements the default behaviour -# as a starting point for convenient modification. -# -# The hook receives the commit with which the tip of the current -# branch is going to be updated: -commit=$1 - -# It can exit with a non-zero status to refuse the push (when it does -# so, it must not modify the index or the working tree). -die () { - echo >&2 "$*" - exit 1 -} - -# Or it can make any necessary changes to the working tree and to the -# index to bring them to the desired state when the tip of the current -# branch is updated to the new commit, and exit with a zero status. -# -# For example, the hook can simply run git read-tree -u -m HEAD "$1" -# in order to emulate git fetch that is run in the reverse direction -# with git push, as the two-tree form of git read-tree -u -m is -# essentially the same as git switch or git checkout that switches -# branches while keeping the local changes in the working tree that do -# not interfere with the difference between the branches. - -# The below is a more-or-less exact translation to shell of the C code -# for the default behaviour for git's push-to-checkout hook defined in -# the push_to_deploy() function in builtin/receive-pack.c. -# -# Note that the hook will be executed from the repository directory, -# not from the working tree, so if you want to perform operations on -# the working tree, you will have to adapt your code accordingly, e.g. -# by adding "cd .." or using relative paths. - -if ! git update-index -q --ignore-submodules --refresh -then - die "Up-to-date check failed" -fi - -if ! git diff-files --quiet --ignore-submodules -- -then - die "Working directory has unstaged changes" -fi - -# This is a rough translation of: -# -# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX -if git cat-file -e HEAD 2>/dev/null -then - head=HEAD -else - head=$(git hash-object -t tree --stdin &2 - exit 1 -} - -unset GIT_DIR GIT_WORK_TREE -cd "$worktree" && - -if grep -q "^diff --git " "$1" -then - validate_patch "$1" -else - validate_cover_letter "$1" -fi && - -if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" -then - git config --unset-all sendemail.validateWorktree && - trap 'git worktree remove -ff "$worktree"' EXIT && - validate_series -fi diff --git a/.git_backup/hooks/update.sample b/.git_backup/hooks/update.sample deleted file mode 100644 index c4d426b..0000000 --- a/.git_backup/hooks/update.sample +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/sh -# -# An example hook script to block unannotated tags from entering. -# Called by "git receive-pack" with arguments: refname sha1-old sha1-new -# -# To enable this hook, rename this file to "update". -# -# Config -# ------ -# hooks.allowunannotated -# This boolean sets whether unannotated tags will be allowed into the -# repository. By default they won't be. -# hooks.allowdeletetag -# This boolean sets whether deleting tags will be allowed in the -# repository. By default they won't be. -# hooks.allowmodifytag -# This boolean sets whether a tag may be modified after creation. By default -# it won't be. -# hooks.allowdeletebranch -# This boolean sets whether deleting branches will be allowed in the -# repository. By default they won't be. -# hooks.denycreatebranch -# This boolean sets whether remotely creating branches will be denied -# in the repository. By default this is allowed. -# - -# --- Command line -refname="$1" -oldrev="$2" -newrev="$3" - -# --- Safety check -if [ -z "$GIT_DIR" ]; then - echo "Don't run this script from the command line." >&2 - echo " (if you want, you could supply GIT_DIR then run" >&2 - echo " $0 )" >&2 - exit 1 -fi - -if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then - echo "usage: $0 " >&2 - exit 1 -fi - -# --- Config -allowunannotated=$(git config --type=bool hooks.allowunannotated) -allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) -denycreatebranch=$(git config --type=bool hooks.denycreatebranch) -allowdeletetag=$(git config --type=bool hooks.allowdeletetag) -allowmodifytag=$(git config --type=bool hooks.allowmodifytag) - -# check for no description -projectdesc=$(sed -e '1q' "$GIT_DIR/description") -case "$projectdesc" in -"Unnamed repository"* | "") - echo "*** Project description file hasn't been set" >&2 - exit 1 - ;; -esac - -# --- Check types -# if $newrev is 0000...0000, it's a commit to delete a ref. -zero=$(git hash-object --stdin &2 - echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 - exit 1 - fi - ;; - refs/tags/*,delete) - # delete tag - if [ "$allowdeletetag" != "true" ]; then - echo "*** Deleting a tag is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/tags/*,tag) - # annotated tag - if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 - then - echo "*** Tag '$refname' already exists." >&2 - echo "*** Modifying a tag is not allowed in this repository." >&2 - exit 1 - fi - ;; - refs/heads/*,commit) - # branch - if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then - echo "*** Creating a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/heads/*,delete) - # delete branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/remotes/*,commit) - # tracking branch - ;; - refs/remotes/*,delete) - # delete tracking branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a tracking branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - *) - # Anything else (is there anything else?) - echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 - exit 1 - ;; -esac - -# --- Finished -exit 0 diff --git a/.git_backup/index b/.git_backup/index deleted file mode 100644 index 6d27d0f..0000000 Binary files a/.git_backup/index and /dev/null differ diff --git a/.git_backup/info/exclude b/.git_backup/info/exclude deleted file mode 100644 index a5196d1..0000000 --- a/.git_backup/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/.git_backup/logs/HEAD b/.git_backup/logs/HEAD deleted file mode 100644 index 9f093bf..0000000 --- a/.git_backup/logs/HEAD +++ /dev/null @@ -1,14 +0,0 @@ -0000000000000000000000000000000000000000 8ed321f2bac0323dabc313a100504ca0a6e9af66 anoracleofra-code 1772689448 -0700 commit (initial): Initial commit: ShadowBroker v0.1 -8ed321f2bac0323dabc313a100504ca0a6e9af66 0000000000000000000000000000000000000000 anoracleofra-code 1772689448 -0700 Branch: renamed refs/heads/master to refs/heads/main -0000000000000000000000000000000000000000 8ed321f2bac0323dabc313a100504ca0a6e9af66 anoracleofra-code 1772689448 -0700 Branch: renamed refs/heads/master to refs/heads/main -8ed321f2bac0323dabc313a100504ca0a6e9af66 3888c91ab35178f779c7fa29f3cb1d33c65116c4 anoracleofra-code 1772691134 -0700 commit: feat: add cross-platform start.sh script and update package.json for macOS/Linux -3888c91ab35178f779c7fa29f3cb1d33c65116c4 e1f4ac2cfb114de61c0d83234b8d2deb545b2301 anoracleofra-code 1773000243 -0600 commit: feat: add Docker publishing via GitHub Actions -e1f4ac2cfb114de61c0d83234b8d2deb545b2301 e1f4ac2cfb114de61c0d83234b8d2deb545b2301 anoracleofra-code 1773000289 -0600 reset: moving to HEAD -e1f4ac2cfb114de61c0d83234b8d2deb545b2301 313aa32a9b08c1ddce4e9c801bdda210e136d67f anoracleofra-code 1773000292 -0600 pull --rebase origin main (start): checkout 313aa32a9b08c1ddce4e9c801bdda210e136d67f -313aa32a9b08c1ddce4e9c801bdda210e136d67f 36c92881c85d3de23f1bb1cbb19d961a3fe2153e anoracleofra-code 1773000292 -0600 pull --rebase origin main (pick): feat: add Docker publishing via GitHub Actions -36c92881c85d3de23f1bb1cbb19d961a3fe2153e 36c92881c85d3de23f1bb1cbb19d961a3fe2153e anoracleofra-code 1773000292 -0600 pull --rebase origin main (finish): returning to refs/heads/main -36c92881c85d3de23f1bb1cbb19d961a3fe2153e 9802fe55a36a903ee68631f48a65d3a2ed180414 anoracleofra-code 1773001228 -0600 commit: fix: make dev scripts cross-platform compatible -9802fe55a36a903ee68631f48a65d3a2ed180414 b57830c1a6b44c36ce83a5acba2bca7b403a2e92 anoracleofra-code 1773001476 -0600 commit: fix: make test_trace.py curl commands OS-agnostic -b57830c1a6b44c36ce83a5acba2bca7b403a2e92 15f1a1dc3ccce735bbeecf4589d935e56254f018 anoracleofra-code 1773001674 -0600 commit: bump: release v0.2.0 -15f1a1dc3ccce735bbeecf4589d935e56254f018 7976602b67bd7bebb98837fcefa21a07ed2de01d anoracleofra-code 1773003311 -0600 commit: fix: integrate AI cross-platform start scripts -7976602b67bd7bebb98837fcefa21a07ed2de01d 5926084a17082014b2ce2aa7ffdcb8367c79975b anoracleofra-code 1773003718 -0600 commit: fix: resolve satellite NORAD ID lookup to fix propagation loop diff --git a/.git_backup/logs/refs/heads/main b/.git_backup/logs/refs/heads/main deleted file mode 100644 index 2f51db1..0000000 --- a/.git_backup/logs/refs/heads/main +++ /dev/null @@ -1,10 +0,0 @@ -0000000000000000000000000000000000000000 8ed321f2bac0323dabc313a100504ca0a6e9af66 anoracleofra-code 1772689448 -0700 commit (initial): Initial commit: ShadowBroker v0.1 -8ed321f2bac0323dabc313a100504ca0a6e9af66 8ed321f2bac0323dabc313a100504ca0a6e9af66 anoracleofra-code 1772689448 -0700 Branch: renamed refs/heads/master to refs/heads/main -8ed321f2bac0323dabc313a100504ca0a6e9af66 3888c91ab35178f779c7fa29f3cb1d33c65116c4 anoracleofra-code 1772691134 -0700 commit: feat: add cross-platform start.sh script and update package.json for macOS/Linux -3888c91ab35178f779c7fa29f3cb1d33c65116c4 e1f4ac2cfb114de61c0d83234b8d2deb545b2301 anoracleofra-code 1773000243 -0600 commit: feat: add Docker publishing via GitHub Actions -e1f4ac2cfb114de61c0d83234b8d2deb545b2301 36c92881c85d3de23f1bb1cbb19d961a3fe2153e anoracleofra-code 1773000292 -0600 pull --rebase origin main (finish): refs/heads/main onto 313aa32a9b08c1ddce4e9c801bdda210e136d67f -36c92881c85d3de23f1bb1cbb19d961a3fe2153e 9802fe55a36a903ee68631f48a65d3a2ed180414 anoracleofra-code 1773001228 -0600 commit: fix: make dev scripts cross-platform compatible -9802fe55a36a903ee68631f48a65d3a2ed180414 b57830c1a6b44c36ce83a5acba2bca7b403a2e92 anoracleofra-code 1773001476 -0600 commit: fix: make test_trace.py curl commands OS-agnostic -b57830c1a6b44c36ce83a5acba2bca7b403a2e92 15f1a1dc3ccce735bbeecf4589d935e56254f018 anoracleofra-code 1773001674 -0600 commit: bump: release v0.2.0 -15f1a1dc3ccce735bbeecf4589d935e56254f018 7976602b67bd7bebb98837fcefa21a07ed2de01d anoracleofra-code 1773003311 -0600 commit: fix: integrate AI cross-platform start scripts -7976602b67bd7bebb98837fcefa21a07ed2de01d 5926084a17082014b2ce2aa7ffdcb8367c79975b anoracleofra-code 1773003718 -0600 commit: fix: resolve satellite NORAD ID lookup to fix propagation loop diff --git a/.git_backup/logs/refs/remotes/origin/main b/.git_backup/logs/refs/remotes/origin/main deleted file mode 100644 index 0fce16c..0000000 --- a/.git_backup/logs/refs/remotes/origin/main +++ /dev/null @@ -1,9 +0,0 @@ -0000000000000000000000000000000000000000 8ed321f2bac0323dabc313a100504ca0a6e9af66 anoracleofra-code 1772690425 -0700 update by push -8ed321f2bac0323dabc313a100504ca0a6e9af66 3888c91ab35178f779c7fa29f3cb1d33c65116c4 anoracleofra-code 1772691136 -0700 update by push -3888c91ab35178f779c7fa29f3cb1d33c65116c4 313aa32a9b08c1ddce4e9c801bdda210e136d67f anoracleofra-code 1773000292 -0600 pull --rebase origin main: fast-forward -313aa32a9b08c1ddce4e9c801bdda210e136d67f 36c92881c85d3de23f1bb1cbb19d961a3fe2153e anoracleofra-code 1773000299 -0600 update by push -36c92881c85d3de23f1bb1cbb19d961a3fe2153e 9802fe55a36a903ee68631f48a65d3a2ed180414 anoracleofra-code 1773001230 -0600 update by push -9802fe55a36a903ee68631f48a65d3a2ed180414 b57830c1a6b44c36ce83a5acba2bca7b403a2e92 anoracleofra-code 1773001477 -0600 update by push -b57830c1a6b44c36ce83a5acba2bca7b403a2e92 15f1a1dc3ccce735bbeecf4589d935e56254f018 anoracleofra-code 1773001675 -0600 update by push -15f1a1dc3ccce735bbeecf4589d935e56254f018 7976602b67bd7bebb98837fcefa21a07ed2de01d anoracleofra-code 1773003313 -0600 update by push -7976602b67bd7bebb98837fcefa21a07ed2de01d 5926084a17082014b2ce2aa7ffdcb8367c79975b anoracleofra-code 1773003720 -0600 update by push diff --git a/.git_backup/objects/00/0c7b3641eea0aa051e1864add55d83992a5357 b/.git_backup/objects/00/0c7b3641eea0aa051e1864add55d83992a5357 deleted file mode 100644 index ae099ff..0000000 Binary files a/.git_backup/objects/00/0c7b3641eea0aa051e1864add55d83992a5357 and /dev/null differ diff --git a/.git_backup/objects/00/4145cddf3f9db91b57b9cb596683c8eb420862 b/.git_backup/objects/00/4145cddf3f9db91b57b9cb596683c8eb420862 deleted file mode 100644 index b03377b..0000000 --- a/.git_backup/objects/00/4145cddf3f9db91b57b9cb596683c8eb420862 +++ /dev/null @@ -1,2 +0,0 @@ -x]Pn0짬ܳyT)=qW!!/%D|}iUxvs#^vww21C#]}|dB -xpD;[,˂v R* "M)%h bH5 >b:J@y^S!/&_;Kr2.'ma͏}I*SJ#i4 صHl+1ҎPӕ]{icnD?[op{h]'yL4IDE;_d \ No newline at end of file diff --git a/.git_backup/objects/00/5d550868a94b04b9b99f5b9dbf5f0ef2902038 b/.git_backup/objects/00/5d550868a94b04b9b99f5b9dbf5f0ef2902038 deleted file mode 100644 index 3e84ae5..0000000 Binary files a/.git_backup/objects/00/5d550868a94b04b9b99f5b9dbf5f0ef2902038 and /dev/null differ diff --git a/.git_backup/objects/00/a1ecd6c2500823b306477925cdda88895339ed b/.git_backup/objects/00/a1ecd6c2500823b306477925cdda88895339ed deleted file mode 100644 index d9b8333..0000000 Binary files a/.git_backup/objects/00/a1ecd6c2500823b306477925cdda88895339ed and /dev/null differ diff --git a/.git_backup/objects/01/e847a3662443a077457309508e69d298187dd5 b/.git_backup/objects/01/e847a3662443a077457309508e69d298187dd5 deleted file mode 100644 index f39a8b7..0000000 Binary files a/.git_backup/objects/01/e847a3662443a077457309508e69d298187dd5 and /dev/null differ diff --git a/.git_backup/objects/03/1f4cd009c88f1884f89e09701c54250db9f7dd b/.git_backup/objects/03/1f4cd009c88f1884f89e09701c54250db9f7dd deleted file mode 100644 index fa7ff9e..0000000 Binary files a/.git_backup/objects/03/1f4cd009c88f1884f89e09701c54250db9f7dd and /dev/null differ diff --git a/.git_backup/objects/04/e7f9f09ac5148e78f35a6944019956da91a8a3 b/.git_backup/objects/04/e7f9f09ac5148e78f35a6944019956da91a8a3 deleted file mode 100644 index 5c219fd..0000000 --- a/.git_backup/objects/04/e7f9f09ac5148e78f35a6944019956da91a8a3 +++ /dev/null @@ -1,2 +0,0 @@ -x+)JMU01c040031QHON--(M,Ыa3|ʄlYaM - \ No newline at end of file diff --git a/.git_backup/objects/05/e726d1b4201bc8c7716d2b058279676582e8c0 b/.git_backup/objects/05/e726d1b4201bc8c7716d2b058279676582e8c0 deleted file mode 100644 index 5a91548..0000000 Binary files a/.git_backup/objects/05/e726d1b4201bc8c7716d2b058279676582e8c0 and /dev/null differ diff --git a/.git_backup/objects/06/24188f733e50f473fcc76fa240603908d17b34 b/.git_backup/objects/06/24188f733e50f473fcc76fa240603908d17b34 deleted file mode 100644 index 19601a1..0000000 --- a/.git_backup/objects/06/24188f733e50f473fcc76fa240603908d17b34 +++ /dev/null @@ -1,2 +0,0 @@ -x5K0 EQYʧ {qST!PwORU=_GP4DxЃ6|`y))eN3]9(b+u}`DcRTRV[T֥B>, Buǎ76|ˀ/T5ڬP)E+bSD0ae-*G=i7"kܪ˫_.gg ŘaR?B[ rs\BCi{5Jv& MI$3L مTsTɭđ|.Ȝt2( =S9ɞ!Нo nN.JZx~t1uMdVUd*,hEZkhFzi! \ No newline at end of file diff --git a/.git_backup/objects/0a/0e8c8841c45541a696194004d79b48cb9e30b2 b/.git_backup/objects/0a/0e8c8841c45541a696194004d79b48cb9e30b2 deleted file mode 100644 index 5af38c8..0000000 Binary files a/.git_backup/objects/0a/0e8c8841c45541a696194004d79b48cb9e30b2 and /dev/null differ diff --git a/.git_backup/objects/0b/54a9eb5b9ddcc67495af3a0ba54a0fe28c5ec3 b/.git_backup/objects/0b/54a9eb5b9ddcc67495af3a0ba54a0fe28c5ec3 deleted file mode 100644 index 5170f7d..0000000 --- a/.git_backup/objects/0b/54a9eb5b9ddcc67495af3a0ba54a0fe28c5ec3 +++ /dev/null @@ -1,3 +0,0 @@ -xePN0X'R T*Q8΢X$^co{юatz,5dgw$1yXpV6jСjA+x- (O+ҒJ(+A1'w@*<'_$=*FP`JOI> Bp --Hٙ -&i:_}x Y\.u L5^Fm]b( 8,M^fpiJVa?lV}gG/F2vyR84|Eч \ No newline at end of file diff --git a/.git_backup/objects/0d/bbcdfd9d4770c10324f4367a23d21fc8a56df8 b/.git_backup/objects/0d/bbcdfd9d4770c10324f4367a23d21fc8a56df8 deleted file mode 100644 index 136b207..0000000 --- a/.git_backup/objects/0d/bbcdfd9d4770c10324f4367a23d21fc8a56df8 +++ /dev/null @@ -1,4 +0,0 @@ -xTkO0C@ljڂ4i@ mBnbe}iӖA7}{g$6߬FBF̦SpeO8u0!>a'] {Me,>Bw:f/K]A7:ѵ %V,c*A34vJxRR g+8 NVX( uLJDK,oDXOmRF\6I; .C{jbvz xhkEש)">OsR//uuQnN} 9=w+&!XWſfɍDNA|w0lr7sFs n+V&w ,vdgkѼEۂ+sAgD{}Ի~/2e<.=KjO,V+++~M)nO'%ifq FN /PO~enG 145~=%%p솶wm,ɥ5)s \ No newline at end of file diff --git a/.git_backup/objects/12/260dec60876b7b074c4a9c5a23a7a8e88ec781 b/.git_backup/objects/12/260dec60876b7b074c4a9c5a23a7a8e88ec781 deleted file mode 100644 index 27f4e02..0000000 Binary files a/.git_backup/objects/12/260dec60876b7b074c4a9c5a23a7a8e88ec781 and /dev/null differ diff --git a/.git_backup/objects/12/d0835a0d871fde9f68f23cdb98812e68dd9c87 b/.git_backup/objects/12/d0835a0d871fde9f68f23cdb98812e68dd9c87 deleted file mode 100644 index b499cec..0000000 Binary files a/.git_backup/objects/12/d0835a0d871fde9f68f23cdb98812e68dd9c87 and /dev/null differ diff --git a/.git_backup/objects/13/e9a744bdf5084cdf9d4c287919a54a992d2b9e b/.git_backup/objects/13/e9a744bdf5084cdf9d4c287919a54a992d2b9e deleted file mode 100644 index 621e1da..0000000 Binary files a/.git_backup/objects/13/e9a744bdf5084cdf9d4c287919a54a992d2b9e and /dev/null differ diff --git a/.git_backup/objects/15/0fd9eca806699b6d42ce4a362309f3e8af3376 b/.git_backup/objects/15/0fd9eca806699b6d42ce4a362309f3e8af3376 deleted file mode 100644 index 0fc521c..0000000 Binary files a/.git_backup/objects/15/0fd9eca806699b6d42ce4a362309f3e8af3376 and /dev/null differ diff --git a/.git_backup/objects/15/f1a1dc3ccce735bbeecf4589d935e56254f018 b/.git_backup/objects/15/f1a1dc3ccce735bbeecf4589d935e56254f018 deleted file mode 100644 index 51f21c6..0000000 --- a/.git_backup/objects/15/f1a1dc3ccce735bbeecf4589d935e56254f018 +++ /dev/null @@ -1,2 +0,0 @@ -x10 @QriB\q]@j -[?Ѯ73'\d*32y ^-'L+,+Qdfy39%VI&,>[N^ZȽ6du18R Z  q`}_Wyf4xtyQ \ No newline at end of file diff --git a/.git_backup/objects/17/bd77c3ce5c05362862da00d24b518757613a79 b/.git_backup/objects/17/bd77c3ce5c05362862da00d24b518757613a79 deleted file mode 100644 index ad21cb9..0000000 --- a/.git_backup/objects/17/bd77c3ce5c05362862da00d24b518757613a79 +++ /dev/null @@ -1,2 +0,0 @@ -x=NMK0_1)hzؓ]Vch)FLM*tǼf4fsD!yx]rUAO= P #֥ZΣ4CVb,ɵV. s*"3%q$~EݩzRp{I"ٌbi:ͥiFڌ~ ^s^Z]W{4!p1Bhnj=䢲Ѝ1S{׍sq&5r2 -F3&0"e6jօt_-)x4VW"_w=55s=o;ђ [v=(gwolRt&|(d*v \ No newline at end of file diff --git a/.git_backup/objects/19/9c8084a456a073d59ed94010c3f75c33c16c34 b/.git_backup/objects/19/9c8084a456a073d59ed94010c3f75c33c16c34 deleted file mode 100644 index 5eb72ed..0000000 Binary files a/.git_backup/objects/19/9c8084a456a073d59ed94010c3f75c33c16c34 and /dev/null differ diff --git a/.git_backup/objects/1a/bc54e1f9e4d27c5b7a784ccc7cd86ca16db854 b/.git_backup/objects/1a/bc54e1f9e4d27c5b7a784ccc7cd86ca16db854 deleted file mode 100644 index cb99ebd..0000000 Binary files a/.git_backup/objects/1a/bc54e1f9e4d27c5b7a784ccc7cd86ca16db854 and /dev/null differ diff --git a/.git_backup/objects/1b/ce9433355ec25dd09d566a5a7c82fc4ccf8fe9 b/.git_backup/objects/1b/ce9433355ec25dd09d566a5a7c82fc4ccf8fe9 deleted file mode 100644 index 2ba8c57..0000000 Binary files a/.git_backup/objects/1b/ce9433355ec25dd09d566a5a7c82fc4ccf8fe9 and /dev/null differ diff --git a/.git_backup/objects/1c/9b7b69d705d0540593a971a0c45d2b71cf4379 b/.git_backup/objects/1c/9b7b69d705d0540593a971a0c45d2b71cf4379 deleted file mode 100644 index 2be0a48..0000000 --- a/.git_backup/objects/1c/9b7b69d705d0540593a971a0c45d2b71cf4379 +++ /dev/null @@ -1,3 +0,0 @@ -xm]O0Wx-*!ĘR -v?w۳@ڵ󜍔|)ExvFת*%Gu+^aU28!m]6v?M!tEP3f`l(&BP -l' e -315>GDi.څciFkA 5T8",꣢ 8R?zCO. v`Vj'MOOwn[<{U,/<HCYyG6z hKzۻZP)BB3 q ҇mŤ{(%mdv?+b:g3tjTTZ\)-h`D9es?k \ No newline at end of file diff --git a/.git_backup/objects/1e/0e6224abbf09c1fd34c008a56d78efef579506 b/.git_backup/objects/1e/0e6224abbf09c1fd34c008a56d78efef579506 deleted file mode 100644 index b93482a..0000000 Binary files a/.git_backup/objects/1e/0e6224abbf09c1fd34c008a56d78efef579506 and /dev/null differ diff --git a/.git_backup/objects/1f/0292bf696045012b3e24496edf2f87b73345ef b/.git_backup/objects/1f/0292bf696045012b3e24496edf2f87b73345ef deleted file mode 100644 index cabdbf5..0000000 Binary files a/.git_backup/objects/1f/0292bf696045012b3e24496edf2f87b73345ef and /dev/null differ diff --git a/.git_backup/objects/1f/21942e66289052ece907f30ab629777e94382a b/.git_backup/objects/1f/21942e66289052ece907f30ab629777e94382a deleted file mode 100644 index 962432a..0000000 Binary files a/.git_backup/objects/1f/21942e66289052ece907f30ab629777e94382a and /dev/null differ diff --git a/.git_backup/objects/1f/c6982631e7cc5c3d60ea0d5199524bb80682aa b/.git_backup/objects/1f/c6982631e7cc5c3d60ea0d5199524bb80682aa deleted file mode 100644 index 0189bf3..0000000 Binary files a/.git_backup/objects/1f/c6982631e7cc5c3d60ea0d5199524bb80682aa and /dev/null differ diff --git a/.git_backup/objects/21/37046acaa8ebd01c7a0f03b69d519277b43a91 b/.git_backup/objects/21/37046acaa8ebd01c7a0f03b69d519277b43a91 deleted file mode 100644 index f36abed..0000000 Binary files a/.git_backup/objects/21/37046acaa8ebd01c7a0f03b69d519277b43a91 and /dev/null differ diff --git a/.git_backup/objects/21/d9bed2477c6f71e758c696b5e5df6b7b678680 b/.git_backup/objects/21/d9bed2477c6f71e758c696b5e5df6b7b678680 deleted file mode 100644 index 5ccf251..0000000 Binary files a/.git_backup/objects/21/d9bed2477c6f71e758c696b5e5df6b7b678680 and /dev/null differ diff --git a/.git_backup/objects/23/05c1df8815315d0aaa7a98195972effdb1fe96 b/.git_backup/objects/23/05c1df8815315d0aaa7a98195972effdb1fe96 deleted file mode 100644 index ccaec1d..0000000 Binary files a/.git_backup/objects/23/05c1df8815315d0aaa7a98195972effdb1fe96 and /dev/null differ diff --git a/.git_backup/objects/23/b7bd3dc49b0779fa09ecb1779220c4a3626bf7 b/.git_backup/objects/23/b7bd3dc49b0779fa09ecb1779220c4a3626bf7 deleted file mode 100644 index ea76d2e..0000000 Binary files a/.git_backup/objects/23/b7bd3dc49b0779fa09ecb1779220c4a3626bf7 and /dev/null differ diff --git a/.git_backup/objects/23/cf0cd2b92bce28c73af6793df233b52c5dea55 b/.git_backup/objects/23/cf0cd2b92bce28c73af6793df233b52c5dea55 deleted file mode 100644 index 1dd3b12..0000000 Binary files a/.git_backup/objects/23/cf0cd2b92bce28c73af6793df233b52c5dea55 and /dev/null differ diff --git a/.git_backup/objects/24/ea56aba9840f1eef220ffa99e736b9e1d3595e b/.git_backup/objects/24/ea56aba9840f1eef220ffa99e736b9e1d3595e deleted file mode 100644 index 90db345..0000000 Binary files a/.git_backup/objects/24/ea56aba9840f1eef220ffa99e736b9e1d3595e and /dev/null differ diff --git a/.git_backup/objects/26/84942ccc6886d499be86d165d9970e94c20ba2 b/.git_backup/objects/26/84942ccc6886d499be86d165d9970e94c20ba2 deleted file mode 100644 index c830520..0000000 --- a/.git_backup/objects/26/84942ccc6886d499be86d165d9970e94c20ba2 +++ /dev/null @@ -1,2 +0,0 @@ -xTn0w,Ch5i5 !`iELhܮflۤ yf(hPM)axRIfM"'>8^9Cl'o)R> y;~89cc|rc )K Uϐ4MuS q%h{ftCjRT$W$u+]#L0M5wcOո-|.=Xބ'F/ 嵿F#Ҷ֒A5-pg<<ƈu ]IQ^ k_;14"\tӒhΠB-Tt%k;Hf%d@v݇Kwb\^_I/cG#p$ ֎MK' -FPœ6t:ʭKETxH9ZySs= Kki)I͢xtS,EݼfKSԊ lBfӫ?vGd2oot-+38J!#Jd!B9BMõ~}|jg0% %ZQSr2:9?+t6&xAޔ3Ư.ػJ,Q]fc߈.\Ώ.O`"vwC[YO+к"ZwO\n\0~ZoL \ No newline at end of file diff --git a/.git_backup/objects/2a/8a913698e7be519d1dfd6a55de6462aee4a993 b/.git_backup/objects/2a/8a913698e7be519d1dfd6a55de6462aee4a993 deleted file mode 100644 index 050a8a9..0000000 Binary files a/.git_backup/objects/2a/8a913698e7be519d1dfd6a55de6462aee4a993 and /dev/null differ diff --git a/.git_backup/objects/2a/983fefcd9103de7a812a1e9d546e1c68939e17 b/.git_backup/objects/2a/983fefcd9103de7a812a1e9d546e1c68939e17 deleted file mode 100644 index 160bb25..0000000 Binary files a/.git_backup/objects/2a/983fefcd9103de7a812a1e9d546e1c68939e17 and /dev/null differ diff --git a/.git_backup/objects/2b/19e1d681ba6d10afec1eebdc51c5d28ade4a0b b/.git_backup/objects/2b/19e1d681ba6d10afec1eebdc51c5d28ade4a0b deleted file mode 100644 index e9c0d68..0000000 --- a/.git_backup/objects/2b/19e1d681ba6d10afec1eebdc51c5d28ade4a0b +++ /dev/null @@ -1,3 +0,0 @@ -xn0sSlV( -FCA`Sm+*N`KJYu\og)=;|! XwOzmWgBPTiߗtڄg͑ؿ_cI -w[W=&ŭOĜ}gabjtu2'?^Q2)8.^B/2*h?Bd.y )>=ZsGwp޳=T T>Gaແ R_؏пjޅuzgāعH-_E:dLJJ;~??2Cz1Pءݾ~V570lѲ'=ulѮ._㿘E[AjR.m/N'n7W#lJ]pˆhzݯbf $vR|xQv7?[ \ No newline at end of file diff --git a/.git_backup/objects/37/d47b3b5706624e48875bf56e96be90369859cf b/.git_backup/objects/37/d47b3b5706624e48875bf56e96be90369859cf deleted file mode 100644 index 4d7fd3a..0000000 Binary files a/.git_backup/objects/37/d47b3b5706624e48875bf56e96be90369859cf and /dev/null differ diff --git a/.git_backup/objects/38/88c91ab35178f779c7fa29f3cb1d33c65116c4 b/.git_backup/objects/38/88c91ab35178f779c7fa29f3cb1d33c65116c4 deleted file mode 100644 index 8c5b531..0000000 Binary files a/.git_backup/objects/38/88c91ab35178f779c7fa29f3cb1d33c65116c4 and /dev/null differ diff --git a/.git_backup/objects/3c/da542952d7268e03f72a5bc0745ce17f714f01 b/.git_backup/objects/3c/da542952d7268e03f72a5bc0745ce17f714f01 deleted file mode 100644 index 64cad4d..0000000 Binary files a/.git_backup/objects/3c/da542952d7268e03f72a5bc0745ce17f714f01 and /dev/null differ diff --git a/.git_backup/objects/3e/b29e9fe30ec9e6f197d4aa12bc7295418310a4 b/.git_backup/objects/3e/b29e9fe30ec9e6f197d4aa12bc7295418310a4 deleted file mode 100644 index 8305fee..0000000 Binary files a/.git_backup/objects/3e/b29e9fe30ec9e6f197d4aa12bc7295418310a4 and /dev/null differ diff --git a/.git_backup/objects/44/14d1d5b6acd54d55e3c22181d72d92f908e333 b/.git_backup/objects/44/14d1d5b6acd54d55e3c22181d72d92f908e333 deleted file mode 100644 index a7445f7..0000000 Binary files a/.git_backup/objects/44/14d1d5b6acd54d55e3c22181d72d92f908e333 and /dev/null differ diff --git a/.git_backup/objects/47/23dc94a9dfddbf665bed15f153f82e7da007aa.REMOVED.git-id b/.git_backup/objects/47/23dc94a9dfddbf665bed15f153f82e7da007aa.REMOVED.git-id deleted file mode 100644 index 5af5cd0..0000000 --- a/.git_backup/objects/47/23dc94a9dfddbf665bed15f153f82e7da007aa.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -b7bd34fbfe4a8a1243afc0989eeac35181208642 \ No newline at end of file diff --git a/.git_backup/objects/4a/4f3e0a71bbc374aa09fbed9c41a386b1bad7fa b/.git_backup/objects/4a/4f3e0a71bbc374aa09fbed9c41a386b1bad7fa deleted file mode 100644 index d302b69..0000000 Binary files a/.git_backup/objects/4a/4f3e0a71bbc374aa09fbed9c41a386b1bad7fa and /dev/null differ diff --git a/.git_backup/objects/4a/fae2283a2d01044e6095543ad0f98d450fc2f0 b/.git_backup/objects/4a/fae2283a2d01044e6095543ad0f98d450fc2f0 deleted file mode 100644 index 8ca08f0..0000000 Binary files a/.git_backup/objects/4a/fae2283a2d01044e6095543ad0f98d450fc2f0 and /dev/null differ diff --git a/.git_backup/objects/4d/986dff7b59b1ef714bd313f08c2c64c61f7d89 b/.git_backup/objects/4d/986dff7b59b1ef714bd313f08c2c64c61f7d89 deleted file mode 100644 index 4ad7b9e..0000000 Binary files a/.git_backup/objects/4d/986dff7b59b1ef714bd313f08c2c64c61f7d89 and /dev/null differ diff --git a/.git_backup/objects/51/74b28c565c285e3e312ec5178be64fbeca8398 b/.git_backup/objects/51/74b28c565c285e3e312ec5178be64fbeca8398 deleted file mode 100644 index 3c78a48..0000000 Binary files a/.git_backup/objects/51/74b28c565c285e3e312ec5178be64fbeca8398 and /dev/null differ diff --git a/.git_backup/objects/52/82c0ebc9b635cca29fe35f4eb4f4b48bef7432 b/.git_backup/objects/52/82c0ebc9b635cca29fe35f4eb4f4b48bef7432 deleted file mode 100644 index 2bcc9ec..0000000 Binary files a/.git_backup/objects/52/82c0ebc9b635cca29fe35f4eb4f4b48bef7432 and /dev/null differ diff --git a/.git_backup/objects/55/99d0c3eb00d4c6374108f5db7e118a8aee8fd6 b/.git_backup/objects/55/99d0c3eb00d4c6374108f5db7e118a8aee8fd6 deleted file mode 100644 index 5ccc3e2..0000000 Binary files a/.git_backup/objects/55/99d0c3eb00d4c6374108f5db7e118a8aee8fd6 and /dev/null differ diff --git a/.git_backup/objects/56/7f17b0d7c7fb662c16d4357dd74830caf2dccb b/.git_backup/objects/56/7f17b0d7c7fb662c16d4357dd74830caf2dccb deleted file mode 100644 index 8732022..0000000 Binary files a/.git_backup/objects/56/7f17b0d7c7fb662c16d4357dd74830caf2dccb and /dev/null differ diff --git a/.git_backup/objects/57/b41a0643e2f6d42b4b739e506579df08d4ef07 b/.git_backup/objects/57/b41a0643e2f6d42b4b739e506579df08d4ef07 deleted file mode 100644 index c3f9ffc..0000000 Binary files a/.git_backup/objects/57/b41a0643e2f6d42b4b739e506579df08d4ef07 and /dev/null differ diff --git a/.git_backup/objects/59/26084a17082014b2ce2aa7ffdcb8367c79975b b/.git_backup/objects/59/26084a17082014b2ce2aa7ffdcb8367c79975b deleted file mode 100644 index 7461ec0..0000000 --- a/.git_backup/objects/59/26084a17082014b2ce2aa7ffdcb8367c79975b +++ /dev/null @@ -1,2 +0,0 @@ -xAN0 EY 7!l؀ ":dǧس}l]s? -#ŀ!Q ęOGqpnhqDGBQc)ВtaEqa xƩ-D/z:mh#Vܒʗ… ^NP>ބڬ{7b \ No newline at end of file diff --git a/.git_backup/objects/59/51bef1b73e359a640fef798c6ac78e77cc8adb b/.git_backup/objects/59/51bef1b73e359a640fef798c6ac78e77cc8adb deleted file mode 100644 index 6e9194a..0000000 Binary files a/.git_backup/objects/59/51bef1b73e359a640fef798c6ac78e77cc8adb and /dev/null differ diff --git a/.git_backup/objects/59/d76c78d2a92c78e1c55738509bb7d3aa96f0e4 b/.git_backup/objects/59/d76c78d2a92c78e1c55738509bb7d3aa96f0e4 deleted file mode 100644 index dfa5df2..0000000 Binary files a/.git_backup/objects/59/d76c78d2a92c78e1c55738509bb7d3aa96f0e4 and /dev/null differ diff --git a/.git_backup/objects/5b/0028f5541bc74a15ed94ce1ff210713f43ba53 b/.git_backup/objects/5b/0028f5541bc74a15ed94ce1ff210713f43ba53 deleted file mode 100644 index 26f6537..0000000 Binary files a/.git_backup/objects/5b/0028f5541bc74a15ed94ce1ff210713f43ba53 and /dev/null differ diff --git a/.git_backup/objects/5c/3b1c768973ca54e9a1befee8dc075f38e8cc56.REMOVED.git-id b/.git_backup/objects/5c/3b1c768973ca54e9a1befee8dc075f38e8cc56.REMOVED.git-id deleted file mode 100644 index 1f41148..0000000 --- a/.git_backup/objects/5c/3b1c768973ca54e9a1befee8dc075f38e8cc56.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -d6b1c5aeaeedd593db374d916b97ba299d7604ad \ No newline at end of file diff --git a/.git_backup/objects/5c/7835f64296b17d7f869701828a0399ab632081 b/.git_backup/objects/5c/7835f64296b17d7f869701828a0399ab632081 deleted file mode 100644 index 4b03ddc..0000000 Binary files a/.git_backup/objects/5c/7835f64296b17d7f869701828a0399ab632081 and /dev/null differ diff --git a/.git_backup/objects/5d/33551b09405e7e252c6a11f080a6c9eca50f6b b/.git_backup/objects/5d/33551b09405e7e252c6a11f080a6c9eca50f6b deleted file mode 100644 index 97715b2..0000000 Binary files a/.git_backup/objects/5d/33551b09405e7e252c6a11f080a6c9eca50f6b and /dev/null differ diff --git a/.git_backup/objects/5e/f6a520780202a1d6addd833d800ccb1ecac0bb b/.git_backup/objects/5e/f6a520780202a1d6addd833d800ccb1ecac0bb deleted file mode 100644 index 61f99d9..0000000 --- a/.git_backup/objects/5e/f6a520780202a1d6addd833d800ccb1ecac0bb +++ /dev/null @@ -1 +0,0 @@ -xePAn ٯK)CU}C\@଺v[U=O1ONja.ݘcсx&jlerrƙz\՚+*;՝V;c19&e5M`tIW[Vn76nO[1mk, dl,, ?^0tT1X`J́>'l0k=N[Tqta_z{?"lRHi_l{%b=g"I0k* =(:/ \ No newline at end of file diff --git a/.git_backup/objects/61/5087732d6676f1536cc0c87acfd647abc4baf6 b/.git_backup/objects/61/5087732d6676f1536cc0c87acfd647abc4baf6 deleted file mode 100644 index 1498a67..0000000 Binary files a/.git_backup/objects/61/5087732d6676f1536cc0c87acfd647abc4baf6 and /dev/null differ diff --git a/.git_backup/objects/61/e36849cf7cfa9f1f71b4a3964a4953e3e243d3 b/.git_backup/objects/61/e36849cf7cfa9f1f71b4a3964a4953e3e243d3 deleted file mode 100644 index d68826c..0000000 Binary files a/.git_backup/objects/61/e36849cf7cfa9f1f71b4a3964a4953e3e243d3 and /dev/null differ diff --git a/.git_backup/objects/64/09c87e4c83b5bb4df12861238b30429c799e8c b/.git_backup/objects/64/09c87e4c83b5bb4df12861238b30429c799e8c deleted file mode 100644 index dfe6e69..0000000 Binary files a/.git_backup/objects/64/09c87e4c83b5bb4df12861238b30429c799e8c and /dev/null differ diff --git a/.git_backup/objects/65/a8c836c47fa38dde71145d3636cf09170bb576 b/.git_backup/objects/65/a8c836c47fa38dde71145d3636cf09170bb576 deleted file mode 100644 index 45467bd..0000000 Binary files a/.git_backup/objects/65/a8c836c47fa38dde71145d3636cf09170bb576 and /dev/null differ diff --git a/.git_backup/objects/66/1cd6b68aaf7f21408e22035b6efd34225e6dff b/.git_backup/objects/66/1cd6b68aaf7f21408e22035b6efd34225e6dff deleted file mode 100644 index d453843..0000000 Binary files a/.git_backup/objects/66/1cd6b68aaf7f21408e22035b6efd34225e6dff and /dev/null differ diff --git a/.git_backup/objects/66/275472a8c50a4d015ea86127d5f59f7327ac28 b/.git_backup/objects/66/275472a8c50a4d015ea86127d5f59f7327ac28 deleted file mode 100644 index c0c7562..0000000 Binary files a/.git_backup/objects/66/275472a8c50a4d015ea86127d5f59f7327ac28 and /dev/null differ diff --git a/.git_backup/objects/68/c8dc533241d3cf02cd0d90a3a758306c576cbb b/.git_backup/objects/68/c8dc533241d3cf02cd0d90a3a758306c576cbb deleted file mode 100644 index aada4c0..0000000 Binary files a/.git_backup/objects/68/c8dc533241d3cf02cd0d90a3a758306c576cbb and /dev/null differ diff --git a/.git_backup/objects/69/091f0d5951ac629ff3ebe75a236a3b41d873d4 b/.git_backup/objects/69/091f0d5951ac629ff3ebe75a236a3b41d873d4 deleted file mode 100644 index f0e1093..0000000 Binary files a/.git_backup/objects/69/091f0d5951ac629ff3ebe75a236a3b41d873d4 and /dev/null differ diff --git a/.git_backup/objects/6b/b27cbcda63b6d5532ba1a74176d204bff4905e b/.git_backup/objects/6b/b27cbcda63b6d5532ba1a74176d204bff4905e deleted file mode 100644 index 2088b8a..0000000 Binary files a/.git_backup/objects/6b/b27cbcda63b6d5532ba1a74176d204bff4905e and /dev/null differ diff --git a/.git_backup/objects/6c/d12d72348931e70eaa51e9dd0dce10580206f1 b/.git_backup/objects/6c/d12d72348931e70eaa51e9dd0dce10580206f1 deleted file mode 100644 index 118323d..0000000 Binary files a/.git_backup/objects/6c/d12d72348931e70eaa51e9dd0dce10580206f1 and /dev/null differ diff --git a/.git_backup/objects/71/8d6fea4835ec2d246af9800eddb7ffb276240c b/.git_backup/objects/71/8d6fea4835ec2d246af9800eddb7ffb276240c deleted file mode 100644 index d1a82db..0000000 Binary files a/.git_backup/objects/71/8d6fea4835ec2d246af9800eddb7ffb276240c and /dev/null differ diff --git a/.git_backup/objects/73/5c9591e90d897975885f0ac318a4ea71ee9078 b/.git_backup/objects/73/5c9591e90d897975885f0ac318a4ea71ee9078 deleted file mode 100644 index 4979079..0000000 Binary files a/.git_backup/objects/73/5c9591e90d897975885f0ac318a4ea71ee9078 and /dev/null differ diff --git a/.git_backup/objects/74/7eddbe1192a234efd4115fec1f7761ef9f5a3a b/.git_backup/objects/74/7eddbe1192a234efd4115fec1f7761ef9f5a3a deleted file mode 100644 index 4e20cbe..0000000 Binary files a/.git_backup/objects/74/7eddbe1192a234efd4115fec1f7761ef9f5a3a and /dev/null differ diff --git a/.git_backup/objects/77/053960334e2e34dc584dea8019925c3b4ccca9 b/.git_backup/objects/77/053960334e2e34dc584dea8019925c3b4ccca9 deleted file mode 100644 index d53a573..0000000 --- a/.git_backup/objects/77/053960334e2e34dc584dea8019925c3b4ccca9 +++ /dev/null @@ -1 +0,0 @@ -x- ~fâ%6 p詿QSQSkbd U.fSc=>Zs^R2F k"M=?n@)ACgWmqm+g^?3 & \ No newline at end of file diff --git a/.git_backup/objects/78/230d26fafbeeddf5ff1d18acbbbd54241597d0 b/.git_backup/objects/78/230d26fafbeeddf5ff1d18acbbbd54241597d0 deleted file mode 100644 index a239ee3..0000000 Binary files a/.git_backup/objects/78/230d26fafbeeddf5ff1d18acbbbd54241597d0 and /dev/null differ diff --git a/.git_backup/objects/79/76602b67bd7bebb98837fcefa21a07ed2de01d b/.git_backup/objects/79/76602b67bd7bebb98837fcefa21a07ed2de01d deleted file mode 100644 index e2e538f..0000000 --- a/.git_backup/objects/79/76602b67bd7bebb98837fcefa21a07ed2de01d +++ /dev/null @@ -1,2 +0,0 @@ -xAn @Ѯ9/ -CPUtc8LWww_7j.\xY\9g׈KH!=3Ka]Ea% !0Wx^M܋_wj+0%o0hyUS ڡr__tn.ph^\> \ No newline at end of file diff --git a/.git_backup/objects/7b/f97de880f4571d867df7ae508fac8f99210d79 b/.git_backup/objects/7b/f97de880f4571d867df7ae508fac8f99210d79 deleted file mode 100644 index a5f40f8..0000000 --- a/.git_backup/objects/7b/f97de880f4571d867df7ae508fac8f99210d79 +++ /dev/null @@ -1,2 +0,0 @@ -x%A0 {+,KC]DQ brq# JP.U:VY%WIb0[ -|d+kvN /|$#_ \ No newline at end of file diff --git a/.git_backup/objects/7e/363b9a8483ca3401cd76718dc404292a59f8f3 b/.git_backup/objects/7e/363b9a8483ca3401cd76718dc404292a59f8f3 deleted file mode 100644 index f7afbce..0000000 Binary files a/.git_backup/objects/7e/363b9a8483ca3401cd76718dc404292a59f8f3 and /dev/null differ diff --git a/.git_backup/objects/81/e81db95466264a126843b76a239068bbc702ff b/.git_backup/objects/81/e81db95466264a126843b76a239068bbc702ff deleted file mode 100644 index 993d0e9..0000000 Binary files a/.git_backup/objects/81/e81db95466264a126843b76a239068bbc702ff and /dev/null differ diff --git a/.git_backup/objects/82/f87f959b25e42d21408358a5bac35ac5830502 b/.git_backup/objects/82/f87f959b25e42d21408358a5bac35ac5830502 deleted file mode 100644 index ae7ece8..0000000 --- a/.git_backup/objects/82/f87f959b25e42d21408358a5bac35ac5830502 +++ /dev/null @@ -1,3 +0,0 @@ -xUN ǽSެM&.K&bOwJO7©q1rs8-՝H<|0d -e/MFq^,)s" -VSJ2#zȞ]hNљgL d=kc -Ȫ&_T{ '8|{ҕK-[/{T^zcj0E\kYNzP}uAF%܈8;M4!<Ɩ]z \ No newline at end of file diff --git a/.git_backup/objects/84/d54e184f5848fff85bf75bc39e92d8c3d192e6 b/.git_backup/objects/84/d54e184f5848fff85bf75bc39e92d8c3d192e6 deleted file mode 100644 index b6d26c8..0000000 Binary files a/.git_backup/objects/84/d54e184f5848fff85bf75bc39e92d8c3d192e6 and /dev/null differ diff --git a/.git_backup/objects/85/e30b4189a8ded6683599400289fcd654228d1d b/.git_backup/objects/85/e30b4189a8ded6683599400289fcd654228d1d deleted file mode 100644 index ba53a26..0000000 Binary files a/.git_backup/objects/85/e30b4189a8ded6683599400289fcd654228d1d and /dev/null differ diff --git a/.git_backup/objects/86/f712609c55d6152694f9971d6ae3e1490247bc b/.git_backup/objects/86/f712609c55d6152694f9971d6ae3e1490247bc deleted file mode 100644 index 304a74d..0000000 Binary files a/.git_backup/objects/86/f712609c55d6152694f9971d6ae3e1490247bc and /dev/null differ diff --git a/.git_backup/objects/87/f9b928e60a398b81b69a46db182b24b1c83436 b/.git_backup/objects/87/f9b928e60a398b81b69a46db182b24b1c83436 deleted file mode 100644 index bd76e42..0000000 Binary files a/.git_backup/objects/87/f9b928e60a398b81b69a46db182b24b1c83436 and /dev/null differ diff --git a/.git_backup/objects/88/2196dab58259aefdc44eaa5a12e7551bc2b821 b/.git_backup/objects/88/2196dab58259aefdc44eaa5a12e7551bc2b821 deleted file mode 100644 index 0dfc579..0000000 Binary files a/.git_backup/objects/88/2196dab58259aefdc44eaa5a12e7551bc2b821 and /dev/null differ diff --git a/.git_backup/objects/88/a1f9a9ba23869e77d4d0fe0012ccab51332cf8 b/.git_backup/objects/88/a1f9a9ba23869e77d4d0fe0012ccab51332cf8 deleted file mode 100644 index 70eeba8..0000000 Binary files a/.git_backup/objects/88/a1f9a9ba23869e77d4d0fe0012ccab51332cf8 and /dev/null differ diff --git a/.git_backup/objects/8b/859758de838ea292cad530d27d46513b76f438 b/.git_backup/objects/8b/859758de838ea292cad530d27d46513b76f438 deleted file mode 100644 index b90cd58..0000000 --- a/.git_backup/objects/8b/859758de838ea292cad530d27d46513b76f438 +++ /dev/null @@ -1 +0,0 @@ -xM= 0+.]A]!oB0_"Tsr.eAk&K֚Ӟ_V1~@oO8} Ǵo=f$Ȳ5"WjöWL \ No newline at end of file diff --git a/.git_backup/objects/9e/26dfeeb6e641a33dae4961196235bdb965b21b b/.git_backup/objects/9e/26dfeeb6e641a33dae4961196235bdb965b21b deleted file mode 100644 index a70f72d..0000000 Binary files a/.git_backup/objects/9e/26dfeeb6e641a33dae4961196235bdb965b21b and /dev/null differ diff --git a/.git_backup/objects/a0/0720b3328f4239daf5c6b99a6fa71a426cd8db b/.git_backup/objects/a0/0720b3328f4239daf5c6b99a6fa71a426cd8db deleted file mode 100644 index b9ff52b..0000000 Binary files a/.git_backup/objects/a0/0720b3328f4239daf5c6b99a6fa71a426cd8db and /dev/null differ diff --git a/.git_backup/objects/a0/14eb86e6d2973813ab56cefd614ea4a58ebd26 b/.git_backup/objects/a0/14eb86e6d2973813ab56cefd614ea4a58ebd26 deleted file mode 100644 index 9578df2..0000000 Binary files a/.git_backup/objects/a0/14eb86e6d2973813ab56cefd614ea4a58ebd26 and /dev/null differ diff --git a/.git_backup/objects/a6/aa56d0aa570437b83a9d66ca75752d412517a7 b/.git_backup/objects/a6/aa56d0aa570437b83a9d66ca75752d412517a7 deleted file mode 100644 index 8bb5911..0000000 Binary files a/.git_backup/objects/a6/aa56d0aa570437b83a9d66ca75752d412517a7 and /dev/null differ diff --git a/.git_backup/objects/a7/8f91b6b6f3539336bf3e2cfbcb068185c98324 b/.git_backup/objects/a7/8f91b6b6f3539336bf3e2cfbcb068185c98324 deleted file mode 100644 index 8a61568..0000000 Binary files a/.git_backup/objects/a7/8f91b6b6f3539336bf3e2cfbcb068185c98324 and /dev/null differ diff --git a/.git_backup/objects/ae/bf936011e394f6336603bab19f128b562b7749 b/.git_backup/objects/ae/bf936011e394f6336603bab19f128b562b7749 deleted file mode 100644 index 406137e..0000000 Binary files a/.git_backup/objects/ae/bf936011e394f6336603bab19f128b562b7749 and /dev/null differ diff --git a/.git_backup/objects/b1/db0b09762d7cfb242ec9d47e250c7d224a32c7 b/.git_backup/objects/b1/db0b09762d7cfb242ec9d47e250c7d224a32c7 deleted file mode 100644 index 0d2a63b..0000000 --- a/.git_backup/objects/b1/db0b09762d7cfb242ec9d47e250c7d224a32c7 +++ /dev/null @@ -1,2 +0,0 @@ -x}Mk0w2T+z|7Y9Z֧ \ No newline at end of file diff --git a/.git_backup/objects/b2/32c3b3d95034683091177b10d182e6d8c898cb b/.git_backup/objects/b2/32c3b3d95034683091177b10d182e6d8c898cb deleted file mode 100644 index 4d2d600..0000000 Binary files a/.git_backup/objects/b2/32c3b3d95034683091177b10d182e6d8c898cb and /dev/null differ diff --git a/.git_backup/objects/b2/b2a44f6ebc70c450043c05a002e7a93ba5d651 b/.git_backup/objects/b2/b2a44f6ebc70c450043c05a002e7a93ba5d651 deleted file mode 100644 index a899069..0000000 --- a/.git_backup/objects/b2/b2a44f6ebc70c450043c05a002e7a93ba5d651 +++ /dev/null @@ -1,2 +0,0 @@ -xj0 (F)#lyt6K~rFs!XO99e -ق_Uʜȕ)$xbh@,oAGlСl'*!Q㵣Gl=يu3lbz{8שf/d</_RIZ?hi[;1MYЄ?_ɰ-m6o^h \ No newline at end of file diff --git a/.git_backup/objects/b4/8bccc5a6daef6971e74f25b18fc22c291d5d66 b/.git_backup/objects/b4/8bccc5a6daef6971e74f25b18fc22c291d5d66 deleted file mode 100644 index 0c107ca..0000000 Binary files a/.git_backup/objects/b4/8bccc5a6daef6971e74f25b18fc22c291d5d66 and /dev/null differ diff --git a/.git_backup/objects/b5/7830c1a6b44c36ce83a5acba2bca7b403a2e92 b/.git_backup/objects/b5/7830c1a6b44c36ce83a5acba2bca7b403a2e92 deleted file mode 100644 index 174ed96..0000000 --- a/.git_backup/objects/b5/7830c1a6b44c36ce83a5acba2bca7b403a2e92 +++ /dev/null @@ -1 +0,0 @@ -xAN0EYsTc;'!n@g\qR=aǞ/RSo` Q(,:@$3uHdN͍nfBu18L٩Xю66NxHU^Ke=Z^ A֮{1yye{_\ \ No newline at end of file diff --git a/.git_backup/objects/b5/f3b08deedb8dc5824c4441201f1e9af7e2b627 b/.git_backup/objects/b5/f3b08deedb8dc5824c4441201f1e9af7e2b627 deleted file mode 100644 index e12fb0f..0000000 Binary files a/.git_backup/objects/b5/f3b08deedb8dc5824c4441201f1e9af7e2b627 and /dev/null differ diff --git a/.git_backup/objects/b8/e30c7d5e5b53bc734ab290a6c0190ef9b37c88 b/.git_backup/objects/b8/e30c7d5e5b53bc734ab290a6c0190ef9b37c88 deleted file mode 100644 index 8bf2832..0000000 Binary files a/.git_backup/objects/b8/e30c7d5e5b53bc734ab290a6c0190ef9b37c88 and /dev/null differ diff --git a/.git_backup/objects/b9/99e70428ef136e95e91d75e925bf4324b10854 b/.git_backup/objects/b9/99e70428ef136e95e91d75e925bf4324b10854 deleted file mode 100644 index 619a354..0000000 Binary files a/.git_backup/objects/b9/99e70428ef136e95e91d75e925bf4324b10854 and /dev/null differ diff --git a/.git_backup/objects/ba/57965389036194d6dd60e6de33d2e1e1bbf20b.REMOVED.git-id b/.git_backup/objects/ba/57965389036194d6dd60e6de33d2e1e1bbf20b.REMOVED.git-id deleted file mode 100644 index 84de059..0000000 --- a/.git_backup/objects/ba/57965389036194d6dd60e6de33d2e1e1bbf20b.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -074351391aceff1fe8e242c5358408e2c96b58df \ No newline at end of file diff --git a/.git_backup/objects/bc/9ce0c146092dcca74f567441821db5052dd1e6 b/.git_backup/objects/bc/9ce0c146092dcca74f567441821db5052dd1e6 deleted file mode 100644 index da2001a..0000000 Binary files a/.git_backup/objects/bc/9ce0c146092dcca74f567441821db5052dd1e6 and /dev/null differ diff --git a/.git_backup/objects/bc/ffc3365a08c3a2a6fb06ce334055e7b5f31626 b/.git_backup/objects/bc/ffc3365a08c3a2a6fb06ce334055e7b5f31626 deleted file mode 100644 index 4e8b774..0000000 Binary files a/.git_backup/objects/bc/ffc3365a08c3a2a6fb06ce334055e7b5f31626 and /dev/null differ diff --git a/.git_backup/objects/bd/e2ebb13ebea949eb4efcd4379a7377671e9730 b/.git_backup/objects/bd/e2ebb13ebea949eb4efcd4379a7377671e9730 deleted file mode 100644 index ef00806..0000000 Binary files a/.git_backup/objects/bd/e2ebb13ebea949eb4efcd4379a7377671e9730 and /dev/null differ diff --git a/.git_backup/objects/bd/f2d4d51da790e75f6d22c6f85a168f6893deeb b/.git_backup/objects/bd/f2d4d51da790e75f6d22c6f85a168f6893deeb deleted file mode 100644 index fc17738..0000000 Binary files a/.git_backup/objects/bd/f2d4d51da790e75f6d22c6f85a168f6893deeb and /dev/null differ diff --git a/.git_backup/objects/bf/9e938d16bb605c04eb9d52c008f4c2e45b612f b/.git_backup/objects/bf/9e938d16bb605c04eb9d52c008f4c2e45b612f deleted file mode 100644 index e64d9bd..0000000 Binary files a/.git_backup/objects/bf/9e938d16bb605c04eb9d52c008f4c2e45b612f and /dev/null differ diff --git a/.git_backup/objects/c0/3712436a6f04d1d2de66481ea3e1ebe5da131c b/.git_backup/objects/c0/3712436a6f04d1d2de66481ea3e1ebe5da131c deleted file mode 100644 index 75f5039..0000000 Binary files a/.git_backup/objects/c0/3712436a6f04d1d2de66481ea3e1ebe5da131c and /dev/null differ diff --git a/.git_backup/objects/c0/85bd39b04f32f89e05277000bfdb333056cdc4 b/.git_backup/objects/c0/85bd39b04f32f89e05277000bfdb333056cdc4 deleted file mode 100644 index 7d7a528..0000000 Binary files a/.git_backup/objects/c0/85bd39b04f32f89e05277000bfdb333056cdc4 and /dev/null differ diff --git a/.git_backup/objects/c1/87027ddeec884723e3e493ce774cb247180835 b/.git_backup/objects/c1/87027ddeec884723e3e493ce774cb247180835 deleted file mode 100644 index 89784f9..0000000 --- a/.git_backup/objects/c1/87027ddeec884723e3e493ce774cb247180835 +++ /dev/null @@ -1,8 +0,0 @@ -xVn8g} 4) A d[,l$Q%M]#Ë$綨"9<3B6ը sTX럩hЧV20!lQuy74U&ySbe0sf ';qb,Ca[7at)`xAw؛ųw@Go7c80ÃDϒˋ r 'k%K%˙ah -wݿոKP2iXVB؎“T ꂙ\C 5&{OjƳl)yK5L0kYCOys%+ JVCQ;HВo6",{8ХEUlbWlҌF%_n+].(,_AhQi*Q9Lp3,4R\w/|l8G(6k)s^+HGImUҎq#%F%\(limUjץ -+iڐG6HiTՅ> -+]#-:拀 3j7 驳#4ᆵ4VDM - -W4;!Z֭K6m"hޏOg%u -lb] D}jւ2,ئc{ߟ_ ӽBNЮB&#yT^cwSrQh:}o[ _Rմ?G]M.ʡ&14:2#%-`6{ShhuxoWLc _E j`]k]7Lܲ|Noű wdB'}v=Mpk6ަ^'ίO'|kCaŠ%xfL;ՔA8vett%v^Vzr{/agZ?0QFBneY\{A[ m/A:cvNf[&}qp@Ii -x%U= ug?: \ No newline at end of file diff --git a/.git_backup/objects/c3/fe14250f915d03e0409344a0845c3c478084f5 b/.git_backup/objects/c3/fe14250f915d03e0409344a0845c3c478084f5 deleted file mode 100644 index 1095837..0000000 Binary files a/.git_backup/objects/c3/fe14250f915d03e0409344a0845c3c478084f5 and /dev/null differ diff --git a/.git_backup/objects/c4/1cffcf2f11e0f73334edd4421799c4a2ae1cff b/.git_backup/objects/c4/1cffcf2f11e0f73334edd4421799c4a2ae1cff deleted file mode 100644 index 314a6ef..0000000 Binary files a/.git_backup/objects/c4/1cffcf2f11e0f73334edd4421799c4a2ae1cff and /dev/null differ diff --git a/.git_backup/objects/c8/0054b58ebb62e1c5b99355269aa9e32dbed78a b/.git_backup/objects/c8/0054b58ebb62e1c5b99355269aa9e32dbed78a deleted file mode 100644 index 59982c6..0000000 Binary files a/.git_backup/objects/c8/0054b58ebb62e1c5b99355269aa9e32dbed78a and /dev/null differ diff --git a/.git_backup/objects/c8/a3e774cfd1bbb2796e43538679f5ed1ececc2e b/.git_backup/objects/c8/a3e774cfd1bbb2796e43538679f5ed1ececc2e deleted file mode 100644 index 15b5bc4..0000000 Binary files a/.git_backup/objects/c8/a3e774cfd1bbb2796e43538679f5ed1ececc2e and /dev/null differ diff --git a/.git_backup/objects/ca/8c2051e45cd5ebf10c5c4a5726c03f87961c6a b/.git_backup/objects/ca/8c2051e45cd5ebf10c5c4a5726c03f87961c6a deleted file mode 100644 index b35b356..0000000 Binary files a/.git_backup/objects/ca/8c2051e45cd5ebf10c5c4a5726c03f87961c6a and /dev/null differ diff --git a/.git_backup/objects/cb/68061f4e0d8fbeeb9f785ed643c3e485e5b3e2 b/.git_backup/objects/cb/68061f4e0d8fbeeb9f785ed643c3e485e5b3e2 deleted file mode 100644 index 82fe496..0000000 Binary files a/.git_backup/objects/cb/68061f4e0d8fbeeb9f785ed643c3e485e5b3e2 and /dev/null differ diff --git a/.git_backup/objects/cb/a2656cff455d0aa6831e515744152e3e823fd3 b/.git_backup/objects/cb/a2656cff455d0aa6831e515744152e3e823fd3 deleted file mode 100644 index bbe6f9b..0000000 Binary files a/.git_backup/objects/cb/a2656cff455d0aa6831e515744152e3e823fd3 and /dev/null differ diff --git a/.git_backup/objects/cc/34776689d701aab8a3cc6994bfc6b3e3803a4f b/.git_backup/objects/cc/34776689d701aab8a3cc6994bfc6b3e3803a4f deleted file mode 100644 index 9f95bca..0000000 --- a/.git_backup/objects/cc/34776689d701aab8a3cc6994bfc6b3e3803a4f +++ /dev/null @@ -1,2 +0,0 @@ -xmSˎ0 yRlyi ?@,nP쿗d rJ!CJfe{˲\5Y~6Z9P}G`xtg&D1:!φ=\}UU7[v8dfRscMsR'U<E2W5ƱvNo'!؉J"$rK0&aD:?fljX.pO%/"ᇻ`WGrHX󚧌A Nʊk)Lhк.iyxh ̒jKygRAa*^@KE엫xR -z-V¸޹FȿBhuKiq|~4kvRvQZmA0 Dzv5gӌ+ bj6-dTC0plwsǒdNGoݍeK]h9 BF'1Sz.7-e}aiRf.lke?u\ -R.r&̫\b f͖p1:\ -QaȰrlLN)!="\^iJbg3PC*$cV{PGƴƣ,t* Uɾ,F"?qwnyHE̥F-UiZ+4%cO 2V/\}bhO} Õ;pDnj 6h:z*_8). \ No newline at end of file diff --git a/.git_backup/objects/f2/5b494952e1d6c1cc51e8e748d92a5704094d13 b/.git_backup/objects/f2/5b494952e1d6c1cc51e8e748d92a5704094d13 deleted file mode 100644 index 0fd67a1..0000000 Binary files a/.git_backup/objects/f2/5b494952e1d6c1cc51e8e748d92a5704094d13 and /dev/null differ diff --git a/.git_backup/objects/f2/c496a74bba365263cfa7e9015b431f06ad24ee b/.git_backup/objects/f2/c496a74bba365263cfa7e9015b431f06ad24ee deleted file mode 100644 index 40ef5ef..0000000 Binary files a/.git_backup/objects/f2/c496a74bba365263cfa7e9015b431f06ad24ee and /dev/null differ diff --git a/.git_backup/objects/f7/94def37de7b989354e76bd5c77b40014d7f37c b/.git_backup/objects/f7/94def37de7b989354e76bd5c77b40014d7f37c deleted file mode 100644 index 57c2421..0000000 Binary files a/.git_backup/objects/f7/94def37de7b989354e76bd5c77b40014d7f37c and /dev/null differ diff --git a/.git_backup/objects/f7/ded8a53398b171034dffd51348d3ec0269a9ce b/.git_backup/objects/f7/ded8a53398b171034dffd51348d3ec0269a9ce deleted file mode 100644 index 26c4432..0000000 Binary files a/.git_backup/objects/f7/ded8a53398b171034dffd51348d3ec0269a9ce and /dev/null differ diff --git a/.git_backup/objects/f7/ea8126c745c0d3479c5e11339a2053357e03c6 b/.git_backup/objects/f7/ea8126c745c0d3479c5e11339a2053357e03c6 deleted file mode 100644 index 84c367e..0000000 --- a/.git_backup/objects/f7/ea8126c745c0d3479c5e11339a2053357e03c6 +++ /dev/null @@ -1 +0,0 @@ -xRj1Y_!$]H/:NhM5Ks04Ddi+n!mD޼yoF ޼= `ˎ6R)I2)*YהpUp#4y3[m._V>t褺'ρ`wDt۶BHGG#q{QWҕº|bh=8J_ TV_,c[ QTk[ORœ;lZ&/:qq6=X}0V'"on7y"IBs/47'1ց \ No newline at end of file diff --git a/.git_backup/objects/f8/695e4f3497b7dd56bf20b2dd38429740e82d89 b/.git_backup/objects/f8/695e4f3497b7dd56bf20b2dd38429740e82d89 deleted file mode 100644 index 0cad8ec..0000000 Binary files a/.git_backup/objects/f8/695e4f3497b7dd56bf20b2dd38429740e82d89 and /dev/null differ diff --git a/.git_backup/objects/f9/1464067002d15a25d5ef9cb4f1cf2f7a1c1bb6 b/.git_backup/objects/f9/1464067002d15a25d5ef9cb4f1cf2f7a1c1bb6 deleted file mode 100644 index bbc2cd2..0000000 --- a/.git_backup/objects/f9/1464067002d15a25d5ef9cb4f1cf2f7a1c1bb6 +++ /dev/null @@ -1,2 +0,0 @@ -xMn ),֍\jcZGܽ08EU G%TSA䐿=H#}!@YIgd@6:ު/xܐe``8ܠy3q -}iR0✶J-eTGcONc8ie_hl`/W|)c|y-gS aņ]T;ܲљuz:kΖеrT EOtϙ\HȄ \ No newline at end of file diff --git a/.git_backup/objects/f9/7282690c47cb3529d24cddd443dc93a36e481d b/.git_backup/objects/f9/7282690c47cb3529d24cddd443dc93a36e481d deleted file mode 100644 index f9e1e9f..0000000 Binary files a/.git_backup/objects/f9/7282690c47cb3529d24cddd443dc93a36e481d and /dev/null differ diff --git a/.git_backup/objects/fb/a474bd16f97fae78a951fb7434b966ac54cb0a b/.git_backup/objects/fb/a474bd16f97fae78a951fb7434b966ac54cb0a deleted file mode 100644 index b872192..0000000 Binary files a/.git_backup/objects/fb/a474bd16f97fae78a951fb7434b966ac54cb0a and /dev/null differ diff --git a/.git_backup/objects/fe/2f7027d9dc88269ac61b45653419f1b612cd57 b/.git_backup/objects/fe/2f7027d9dc88269ac61b45653419f1b612cd57 deleted file mode 100644 index 815700c..0000000 Binary files a/.git_backup/objects/fe/2f7027d9dc88269ac61b45653419f1b612cd57 and /dev/null differ diff --git a/.git_backup/objects/fe/5c684fa41ccf3103291717f49208c8a33fb314 b/.git_backup/objects/fe/5c684fa41ccf3103291717f49208c8a33fb314 deleted file mode 100644 index 3dcc283..0000000 Binary files a/.git_backup/objects/fe/5c684fa41ccf3103291717f49208c8a33fb314 and /dev/null differ diff --git a/.git_backup/objects/fe/b48415c2e37cc043093fe166fa8ec7be3ce6d4 b/.git_backup/objects/fe/b48415c2e37cc043093fe166fa8ec7be3ce6d4 deleted file mode 100644 index 9b2f519..0000000 --- a/.git_backup/objects/fe/b48415c2e37cc043093fe166fa8ec7be3ce6d4 +++ /dev/null @@ -1 +0,0 @@ -xMK0=W4-O= &&_lxLgwl-.Osֳ/ay3qfd /OL/ӖٕyNV6pyQEsFQF14RyY:55PW}6e=.RoύJƾð5;|p l}<'*3?bm/߉juwxk/" \ No newline at end of file diff --git a/.git_backup/objects/ff/faa9634730cb45f160b3ce6e0b583a6be55239 b/.git_backup/objects/ff/faa9634730cb45f160b3ce6e0b583a6be55239 deleted file mode 100644 index b53a4af..0000000 Binary files a/.git_backup/objects/ff/faa9634730cb45f160b3ce6e0b583a6be55239 and /dev/null differ diff --git a/.git_backup/refs/heads/main b/.git_backup/refs/heads/main deleted file mode 100644 index 807f984..0000000 --- a/.git_backup/refs/heads/main +++ /dev/null @@ -1 +0,0 @@ -5926084a17082014b2ce2aa7ffdcb8367c79975b diff --git a/.git_backup/refs/remotes/origin/main b/.git_backup/refs/remotes/origin/main deleted file mode 100644 index 807f984..0000000 --- a/.git_backup/refs/remotes/origin/main +++ /dev/null @@ -1 +0,0 @@ -5926084a17082014b2ce2aa7ffdcb8367c79975b diff --git a/.gitignore b/.gitignore index ff8463c..47dc5dc 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,21 @@ backend/dump.json backend/debug_fast.json *.zip .git_backup/ + +# Test files (may contain hardcoded keys) +backend/test_*.py +backend/services/test_*.py + +# Local analysis & dev tools +backend/analyze_xlsx.py +backend/xlsx_analysis.txt +backend/services/ais_cache.json + +# Internal update tracking (not for repo) +updatestuff.md + +# Misc dev artifacts +clean_zip.py +zip_repo.py +refactor_cesium.py +jobs.json diff --git a/README.md b/README.md index 615635b..f365f10 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,19 @@ Open `http://localhost:3000` to view the dashboard! *(Requires Docker)* * Head of state & government type (Wikidata SPARQL) * Local Wikipedia summary with thumbnail +### 🛰️ Satellite Imagery + +* **NASA GIBS (MODIS Terra)** — Daily true-color satellite imagery overlay with 30-day time slider, play/pause animation, and opacity control (~250m/pixel) +* **High-Res Satellite (Esri)** — Sub-meter resolution imagery via Esri World Imagery — zoom into buildings and terrain detail (zoom 18+) +* **Sentinel-2 Intel Card** — Right-click anywhere on the map for a floating intel card showing the latest Sentinel-2 satellite photo with capture date, cloud cover %, and clickable full-resolution image (10m resolution, updated every ~5 days) +* **SATELLITE Style Preset** — Quick-toggle high-res imagery via the STYLE button (DEFAULT → SATELLITE → FLIR → NVG → CRT) + +### 📻 Software-Defined Radio (SDR) + +* **KiwiSDR Receivers** — 500+ public SDR receivers plotted worldwide with clustered amber markers +* **Live Radio Tuner** — Click any KiwiSDR node to open an embedded SDR tuner directly in the SIGINT panel +* **Metadata Display** — Node name, location, antenna type, frequency bands, active users + ### 📷 Surveillance * **CCTV Mesh** — 2,000+ live traffic cameras from: @@ -99,6 +112,7 @@ Open `http://localhost:3000` to view the dashboard! *(Requires Docker)* * **Day/Night Cycle** — Solar terminator overlay showing global daylight/darkness * **Global Markets Ticker** — Live financial market indices (minimizable) * **Measurement Tool** — Point-to-point distance & bearing measurement on the map +* **LOCATE Bar** — Search by coordinates (31.8, 34.8) or place name (Tehran, Strait of Hormuz) to fly directly to any location — geocoded via OpenStreetMap Nominatim --- @@ -155,6 +169,11 @@ Open `http://localhost:3000` to view the dashboard! *(Requires Docker)* | [RestCountries](https://restcountries.com) | Country profile data | On-demand (cached 24h) | No | | [Wikidata SPARQL](https://query.wikidata.org) | Head of state data | On-demand (cached 24h) | No | | [Wikipedia API](https://en.wikipedia.org/api) | Location summaries & aircraft images | On-demand (cached) | No | +| [NASA GIBS](https://gibs.earthdata.nasa.gov) | MODIS Terra daily satellite imagery | Daily (24-48h delay) | No | +| [Esri World Imagery](https://www.arcgis.com) | High-res satellite basemap | Static (periodically updated) | No | +| [MS Planetary Computer](https://planetarycomputer.microsoft.com) | Sentinel-2 L2A scenes (right-click) | On-demand | No | +| [KiwiSDR](https://kiwisdr.com) | Public SDR receiver locations | ~30min | No | +| [OSM Nominatim](https://nominatim.openstreetmap.org) | Place name geocoding (LOCATE bar) | On-demand | No | | [CARTO Basemaps](https://carto.com) | Dark map tiles | Continuous | No | --- @@ -176,12 +195,23 @@ docker-compose up -d --build Open `http://localhost:3000` to view the dashboard. -> **Custom ports or LAN access?** The frontend auto-detects the backend at -> `:8000`. If you remap the backend to a different port -> (e.g. `"9096:8000"`), set `NEXT_PUBLIC_API_URL` before building: +> **Deploying publicly or on a LAN?** The frontend **auto-detects** the +> backend — it uses your browser's hostname with port `8000` +> (e.g. if you visit `http://192.168.1.50:3000`, API calls go to +> `http://192.168.1.50:8000`). **No configuration needed** for most setups. +> +> If your backend runs on a **different port or host** (reverse proxy, +> custom Docker port mapping, separate server), set `NEXT_PUBLIC_API_URL`: > > ```bash -> NEXT_PUBLIC_API_URL=http://192.168.1.50:9096 docker-compose up -d --build +> # Linux / macOS +> NEXT_PUBLIC_API_URL=http://myserver.com:9096 docker-compose up -d --build +> +> # Windows (PowerShell) +> $env:NEXT_PUBLIC_API_URL="http://myserver.com:9096"; docker-compose up -d --build +> +> # Or add to a .env file next to docker-compose.yml: +> # NEXT_PUBLIC_API_URL=http://myserver.com:9096 > ``` > > This is a **build-time** variable (Next.js limitation) — it gets baked into @@ -225,7 +255,7 @@ cd backend python -m venv venv venv\Scripts\activate # Windows # source venv/bin/activate # macOS/Linux -pip install -r requirements.txt +pip install -r requirements.txt # includes pystac-client for Sentinel-2 # Create .env with your API keys echo "AIS_API_KEY=your_aisstream_key" >> .env @@ -271,6 +301,9 @@ All layers are independently toggleable from the left panel: | Ukraine Frontline | ✅ ON | Live warfront positions | | Global Incidents | ✅ ON | GDELT conflict events | | GPS Jamming | ✅ ON | NAC-P degradation zones | +| MODIS Terra (Daily) | ❌ OFF | NASA GIBS daily satellite imagery | +| High-Res Satellite | ❌ OFF | Esri sub-meter satellite imagery | +| KiwiSDR Receivers | ❌ OFF | Public SDR radio receivers | | Day / Night Cycle | ✅ ON | Solar terminator overlay | --- @@ -306,6 +339,8 @@ live-risk-dashboard/ │ ├── geopolitics.py # GDELT + Ukraine frontline fetcher │ ├── region_dossier.py # Right-click country/city intelligence │ ├── radio_intercept.py # Scanner radio feed integration +│ ├── kiwisdr_fetcher.py # KiwiSDR receiver scraper +│ ├── sentinel_search.py # Sentinel-2 STAC imagery search │ ├── network_utils.py # HTTP client with curl fallback │ └── api_settings.py # API key management │ @@ -324,6 +359,7 @@ live-risk-dashboard/ │ │ ├── MarketsPanel.tsx # Global financial markets ticker │ │ ├── RadioInterceptPanel.tsx # Scanner-style radio panel │ │ ├── FindLocateBar.tsx # Search/locate bar +│ │ ├── ChangelogModal.tsx # Version changelog popup │ │ ├── SettingsPanel.tsx # App settings │ │ ├── ScaleBar.tsx # Map scale indicator │ │ ├── WikiImage.tsx # Wikipedia image fetcher @@ -335,7 +371,7 @@ live-risk-dashboard/ ## 🔑 Environment Variables -Create a `.env` file in the `backend/` directory: +### Backend (`backend/.env`) ```env # Required @@ -347,6 +383,17 @@ OPENSKY_CLIENT_SECRET=your_opensky_secret # OAuth2 — paired with Client ID LTA_ACCOUNT_KEY=your_lta_key # Singapore CCTV cameras ``` +### Frontend (optional) + +| Variable | Where to set | Purpose | +|---|---|---| +| `NEXT_PUBLIC_API_URL` | `.env` next to `docker-compose.yml`, or shell env | Override backend URL when deploying publicly or behind a reverse proxy. Leave unset for auto-detection. | + +**How auto-detection works:** When `NEXT_PUBLIC_API_URL` is not set, the frontend +reads `window.location.hostname` in the browser and calls `{protocol}//{hostname}:8000`. +This means the dashboard works on `localhost`, LAN IPs, and public domains without +any configuration — as long as the backend is reachable on port 8000 of the same host. + --- ## ⚠️ Disclaimer diff --git a/backend/analyze_xlsx.py b/backend/analyze_xlsx.py deleted file mode 100644 index da601b8..0000000 --- a/backend/analyze_xlsx.py +++ /dev/null @@ -1,112 +0,0 @@ -import zipfile -import xml.etree.ElementTree as ET -import re -import csv -import os - -xlsx_path = r"f:\Codebase\Oracle\live-risk-dashboard\TheAirTraffic Database.xlsx" -output_path = r"f:\Codebase\Oracle\live-risk-dashboard\backend\xlsx_analysis.txt" - -def parse_xlsx_sheet(z, shared_strings, sheet_num): - ns = {'s': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'} - sheet_file = f'xl/worksheets/sheet{sheet_num}.xml' - if sheet_file not in z.namelist(): - return [] - ws_xml = z.read(sheet_file) - ws_root = ET.fromstring(ws_xml) - rows = [] - for row in ws_root.findall('.//s:sheetData/s:row', ns): - cells = {} - for cell in row.findall('s:c', ns): - cell_ref = cell.get('r', '') - cell_type = cell.get('t', '') - val_elem = cell.find('s:v', ns) - val = val_elem.text if val_elem is not None else '' - if cell_type == 's' and val: - val = shared_strings[int(val)] - col = re.match(r'([A-Z]+)', cell_ref).group(1) if re.match(r'([A-Z]+)', cell_ref) else '' - cells[col] = val - rows.append(cells) - return rows - -with open(output_path, 'w', encoding='utf-8') as out: - with zipfile.ZipFile(xlsx_path, 'r') as z: - shared_strings = [] - if 'xl/sharedStrings.xml' in z.namelist(): - ss_xml = z.read('xl/sharedStrings.xml') - root = ET.fromstring(ss_xml) - ns = {'s': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'} - for si in root.findall('.//s:si', ns): - texts = si.findall('.//s:t', ns) - val = ''.join(t.text or '' for t in texts) - shared_strings.append(val) - - all_entries = [] - for sheet_idx in range(1, 5): - rows = parse_xlsx_sheet(z, shared_strings, sheet_idx) - if not rows: - continue - - out.write(f"\n=== SHEET {sheet_idx}: {len(rows)} rows ===\n") - # Print first 5 rows - for i in range(min(5, len(rows))): - for col in sorted(rows[i].keys(), key=lambda x: (len(x), x)): - val = rows[i][col] - if val: - out.write(f" Row{i} {col}: '{val[:80]}'\n") - out.write("\n") - - for r in rows[1:]: - for col, val in r.items(): - val = str(val).strip() - n_regs = re.findall(r'N\d{1,5}[A-Z]{0,2}', val) - owner = r.get('B', r.get('A', '')).strip() - aircraft_type = r.get('C', r.get('D', '')).strip() - for reg in n_regs: - all_entries.append({ - 'registration': reg.upper(), - 'owner': owner, - 'type': aircraft_type, - 'sheet': sheet_idx - }) - - unique_regs = set(e['registration'] for e in all_entries) - out.write(f"\nTOTAL ENTRIES: {len(all_entries)}\n") - out.write(f"UNIQUE REGISTRATIONS: {len(unique_regs)}\n") - - csv_path = r"f:\Codebase\Oracle\live-risk-dashboard\PLANEALERTLIST\plane-alert-db-main\plane-alert-db.csv" - existing = {} - with open(csv_path, 'r', encoding='utf-8') as f: - reader = csv.DictReader(f) - for row in reader: - icao = row.get('$ICAO', '').strip().upper() - reg = row.get('$Registration', '').strip().upper() - if reg: - existing[reg] = { - 'icao': icao, - 'category': row.get('Category', ''), - 'operator': row.get('$Operator', ''), - } - - already_in = unique_regs & set(existing.keys()) - missing = unique_regs - set(existing.keys()) - out.write(f"\nplane-alert-db: {len(existing)} registrations\n") - out.write(f"Already covered: {len(already_in)}\n") - out.write(f"MISSING: {len(missing)}\n") - - out.write(f"\n--- ALREADY TRACKED ---\n") - seen = set() - for e in all_entries: - if e['registration'] in already_in and e['registration'] not in seen: - info = existing[e['registration']] - out.write(f" {e['owner'][:40]:40s} {e['registration']:10s} DB_CAT: {info['category'][:25]:25s} DB_OP: {info['operator'][:40]}\n") - seen.add(e['registration']) - - out.write(f"\n--- MISSING (NEED TO ADD) ---\n") - seen = set() - for e in all_entries: - if e['registration'] in missing and e['registration'] not in seen: - out.write(f" {e['owner'][:40]:40s} {e['registration']:10s} TYPE: {e['type'][:30]}\n") - seen.add(e['registration']) - -print(f"Analysis written to {output_path}") diff --git a/backend/main.py b/backend/main.py index b13ddc1..e830df9 100644 --- a/backend/main.py +++ b/backend/main.py @@ -92,7 +92,8 @@ async def live_data_slow(request: Request): "frontlines": d.get("frontlines"), "gdelt": d.get("gdelt", []), "airports": d.get("airports", []), - "satellites": d.get("satellites", []) + "satellites": d.get("satellites", []), + "kiwisdr": d.get("kiwisdr", []) } # ETag based on last_updated + item counts last_updated = d.get("last_updated", "") @@ -187,6 +188,13 @@ def api_region_dossier(lat: float, lng: float): """Sync def so FastAPI runs it in a threadpool — prevents blocking the event loop.""" return get_region_dossier(lat, lng) +from services.sentinel_search import search_sentinel2_scene + +@app.get("/api/sentinel2/search") +def api_sentinel2_search(lat: float, lng: float): + """Search for latest Sentinel-2 imagery at a point. Sync for threadpool execution.""" + return search_sentinel2_scene(lat, lng) + # --------------------------------------------------------------------------- # API Settings — key registry & management # --------------------------------------------------------------------------- diff --git a/backend/requirements.txt b/backend/requirements.txt index c8d1f08..47b0998 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -17,3 +17,4 @@ reverse_geocoder>=1.5 sgp4>=2.23 geopy>=2.4.0 pytz>=2023.3 +pystac-client>=0.7.0 diff --git a/backend/services/ais_cache.json.REMOVED.git-id b/backend/services/ais_cache.json.REMOVED.git-id deleted file mode 100644 index 55b5e96..0000000 --- a/backend/services/ais_cache.json.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -575f12564b3de1bc4889e7ab818bb1bfc3f3fa32 \ No newline at end of file diff --git a/backend/services/data_fetcher.py b/backend/services/data_fetcher.py index 9d2c72c..0497f19 100644 --- a/backend/services/data_fetcher.py +++ b/backend/services/data_fetcher.py @@ -100,7 +100,8 @@ latest_data = { "uavs": [], "frontlines": None, "gdelt": [], - "liveuamap": [] + "liveuamap": [], + "kiwisdr": [] } # Thread lock for safe reads/writes to latest_data @@ -1250,6 +1251,14 @@ def fetch_cctv(): logger.error(f"Error fetching cctv from DB: {e}") latest_data["cctv"] = [] +def fetch_kiwisdr(): + try: + from services.kiwisdr_fetcher import fetch_kiwisdr_nodes + latest_data["kiwisdr"] = fetch_kiwisdr_nodes() + except Exception as e: + logger.error(f"Error fetching KiwiSDR nodes: {e}") + latest_data["kiwisdr"] = [] + def fetch_bikeshare(): bikes = [] try: @@ -1805,6 +1814,7 @@ def update_slow_data(): fetch_cctv, fetch_earthquakes, fetch_geopolitics, + fetch_kiwisdr, ] with concurrent.futures.ThreadPoolExecutor(max_workers=len(slow_funcs)) as executor: futures = [executor.submit(func) for func in slow_funcs] diff --git a/backend/services/kiwisdr_fetcher.py b/backend/services/kiwisdr_fetcher.py new file mode 100644 index 0000000..3939085 --- /dev/null +++ b/backend/services/kiwisdr_fetcher.py @@ -0,0 +1,97 @@ +""" +KiwiSDR public receiver list fetcher. +Scrapes the kiwisdr.com public page for active SDR receivers worldwide. +Data is embedded as HTML comments inside each entry div. +""" + +import re +import logging +from cachetools import TTLCache, cached + +logger = logging.getLogger(__name__) + +kiwisdr_cache = TTLCache(maxsize=1, ttl=600) # 10-minute cache + + +def _parse_comment(html: str, field: str) -> str: + """Extract a field value from HTML comment like """ + m = re.search(rf'', html) + return m.group(1).strip() if m else "" + + +def _parse_gps(html: str): + """Extract lat/lon from comment.""" + m = re.search(r'', html) + if m: + try: + return float(m.group(1)), float(m.group(2)) + except ValueError: + return None, None + return None, None + + +@cached(kiwisdr_cache) +def fetch_kiwisdr_nodes() -> list[dict]: + """Fetch and parse the KiwiSDR public receiver list.""" + from services.network_utils import smart_request + + try: + res = smart_request("http://kiwisdr.com/.public/", timeout=20) + if not res or res.status_code != 200: + logger.error(f"KiwiSDR fetch failed: HTTP {res.status_code if res else 'no response'}") + return [] + + html = res.text + # Split by entry divs + entries = re.findall(r"
(.*?)
\s*", html, re.DOTALL) + + nodes = [] + for entry in entries: + lat, lon = _parse_gps(entry) + if lat is None or lon is None: + continue + if abs(lat) > 90 or abs(lon) > 180: + continue + + offline = _parse_comment(entry, "offline") + if offline == "yes": + continue + + name = _parse_comment(entry, "name") or "Unknown SDR" + users_str = _parse_comment(entry, "users") + users_max_str = _parse_comment(entry, "users_max") + bands = _parse_comment(entry, "bands") + antenna = _parse_comment(entry, "antenna") + location = _parse_comment(entry, "loc") + + # Extract the URL from the href + url_match = re.search(r"href='(https?://[^']+)'", entry) + url = url_match.group(1) if url_match else "" + + try: + users = int(users_str) if users_str else 0 + except ValueError: + users = 0 + try: + users_max = int(users_max_str) if users_max_str else 0 + except ValueError: + users_max = 0 + + nodes.append({ + "name": name[:120], # Truncate long names + "lat": round(lat, 5), + "lon": round(lon, 5), + "url": url, + "users": users, + "users_max": users_max, + "bands": bands, + "antenna": antenna[:200] if antenna else "", + "location": location[:100] if location else "", + }) + + logger.info(f"KiwiSDR: parsed {len(nodes)} online receivers") + return nodes + + except Exception as e: + logger.error(f"KiwiSDR fetch exception: {e}") + return [] diff --git a/backend/services/sentinel_search.py b/backend/services/sentinel_search.py new file mode 100644 index 0000000..ab5130d --- /dev/null +++ b/backend/services/sentinel_search.py @@ -0,0 +1,81 @@ +""" +Sentinel-2 satellite imagery search via Microsoft Planetary Computer STAC API. +Free, keyless search for metadata + thumbnails. Used in the right-click dossier. +""" + +import logging +from datetime import datetime, timedelta +from cachetools import TTLCache + +logger = logging.getLogger(__name__) + +# Cache by rounded lat/lon (0.02° grid ~= 2km), TTL 1 hour +_sentinel_cache = TTLCache(maxsize=200, ttl=3600) + + +def search_sentinel2_scene(lat: float, lng: float) -> dict: + """Search for the latest Sentinel-2 L2A scene covering a point.""" + cache_key = f"{round(lat, 2)}_{round(lng, 2)}" + if cache_key in _sentinel_cache: + return _sentinel_cache[cache_key] + + try: + from pystac_client import Client + + catalog = Client.open("https://planetarycomputer.microsoft.com/api/stac/v1") + end = datetime.utcnow() + start = end - timedelta(days=30) + + search = catalog.search( + collections=["sentinel-2-l2a"], + intersects={"type": "Point", "coordinates": [lng, lat]}, + datetime=f"{start.isoformat()}Z/{end.isoformat()}Z", + sortby=[{"field": "datetime", "direction": "desc"}], + max_items=3, + query={"eo:cloud_cover": {"lt": 30}}, + ) + + items = list(search.items()) + if not items: + result = {"found": False, "message": "No clear scenes in last 30 days"} + _sentinel_cache[cache_key] = result + return result + + item = items[0] + # Try to sign item first for Azure blob URLs + try: + import planetary_computer + item = planetary_computer.sign_item(item) + except ImportError: + pass # planetary_computer not installed, try unsigned URLs + except Exception as e: + logger.warning(f"Sentinel-2 signing failed: {e}") + + # Get the rendered_preview (full-res PNG) and thumbnail separately + rendered = item.assets.get("rendered_preview") + thumbnail = item.assets.get("thumbnail") + + # Full-res image URL — what opens when user clicks + fullres_url = rendered.href if rendered else (thumbnail.href if thumbnail else None) + # Thumbnail URL — what shows in the popup card + thumb_url = thumbnail.href if thumbnail else (rendered.href if rendered else None) + + result = { + "found": True, + "scene_id": item.id, + "datetime": item.datetime.isoformat() if item.datetime else None, + "cloud_cover": item.properties.get("eo:cloud_cover"), + "thumbnail_url": thumb_url, + "fullres_url": fullres_url, + "bbox": list(item.bbox) if item.bbox else None, + "platform": item.properties.get("platform", "Sentinel-2"), + } + _sentinel_cache[cache_key] = result + return result + + except ImportError: + logger.warning("pystac-client not installed — Sentinel-2 search unavailable") + return {"found": False, "error": "pystac-client not installed"} + except Exception as e: + logger.error(f"Sentinel-2 search failed for ({lat}, {lng}): {e}") + return {"found": False, "error": str(e)} diff --git a/backend/services/test_flights.py b/backend/services/test_flights.py deleted file mode 100644 index 92454ce..0000000 --- a/backend/services/test_flights.py +++ /dev/null @@ -1,17 +0,0 @@ -import sys -import logging -logging.basicConfig(level=logging.DEBUG) - -# Add backend directory to sys path so we can import modules -sys.path.append(r'f:\Codebase\Oracle\live-risk-dashboard\backend') - -from services.data_fetcher import fetch_flights, latest_data - -print("Testing fetch_flights...") -try: - fetch_flights() - print("Commercial flights count:", len(latest_data.get('commercial_flights', []))) - print("Private jets count:", len(latest_data.get('private_jets', []))) -except Exception as e: - import traceback - traceback.print_exc() diff --git a/backend/services/test_liveuamap.py b/backend/services/test_liveuamap.py deleted file mode 100644 index 938975d..0000000 --- a/backend/services/test_liveuamap.py +++ /dev/null @@ -1,38 +0,0 @@ -import json -from playwright.sync_api import sync_playwright - -def scrape_liveuamap(): - print("Launching playwright...") - with sync_playwright() as p: - # User agents are important for headless browsing - browser = p.chromium.launch(headless=True) - page = browser.new_page(user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") - - def handle_response(response): - try: - if not response.url.endswith(('js', 'css', 'png', 'jpg', 'woff2', 'svg', 'ico')): - print(f"Intercepted API Call: {response.url}") - except Exception: - pass - - page.on("response", handle_response) - - print("Navigating to liveuamap...") - try: - page.goto("https://liveuamap.com/", timeout=30000, wait_until="domcontentloaded") - page.wait_for_timeout(5000) - - print("Grabbing all script tags...") - scripts = page.evaluate("() => Array.from(document.querySelectorAll('script')).map(s => s.innerText)") - for i, s in enumerate(scripts): - if 'JSON.parse' in s or 'markers' in s or 'JSON' in s: - with open(f"script_{i}.txt", "w", encoding="utf-8") as f: - f.write(s) - except Exception as e: - print("Playwright timeout or error:", e) - - print("Closing browser...") - browser.close() - -if __name__ == "__main__": - scrape_liveuamap() diff --git a/backend/services/test_openmhz_scraper.py b/backend/services/test_openmhz_scraper.py deleted file mode 100644 index c187027..0000000 --- a/backend/services/test_openmhz_scraper.py +++ /dev/null @@ -1,59 +0,0 @@ -import requests -import json -import time -import cloudscraper - -def scrape_openmhz_systems(): - print("Testing OpenMHZ undocumented API with Cloudscraper...") - headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - } - - scraper = cloudscraper.create_scraper(browser={'browser': 'chrome', 'platform': 'windows', 'desktop': True}) - - try: - # Step 1: Hit the public systems list that the front-end map uses - res = scraper.get("https://api.openmhz.com/systems", headers=headers, timeout=15) - json_data = res.json() - systems = json_data.get('systems', []) if isinstance(json_data, dict) else [] - print(f"Successfully spoofed OpenMHZ frontend. Found {len(systems)} active police/fire systems.") - - if not systems: - return - - # Inspect the first system (usually a major city) - city = systems[0] - sys_name = city.get('shortName') - print(f"Targeting System: {city.get('name')} ({sys_name})") - - if not sys_name: - return - - time.sleep(2) # Ethical delay - - # Step 2: Query the recent calls for this specific system - # The frontend queries: https://api.openmhz.com//calls - calls_url = f"https://api.openmhz.com/{sys_name}/calls" - print(f"Fetching recent bursts: {calls_url}") - - call_res = scraper.get(calls_url, headers=headers, timeout=15) - - if call_res.status_code == 200: - call_json = call_res.json() - calls = call_json.get('calls', []) if isinstance(call_json, dict) else [] - if calls and len(calls) > 0: - print(f"Intercepted {len(calls)} audio bursts.") - latest = calls[0] - print("LATEST INTERCEPT:") - print(f"Talkgroup: {latest.get('talkgroupNum')}") - print(f"Audio URL: {latest.get('url')}") - else: - print("No recent calls found for this system.") - else: - print(f"Failed to fetch calls. HTTP {call_res.status_code}") - - except Exception as e: - print(f"Scrape Exception: {e}") - -if __name__ == "__main__": - scrape_openmhz_systems() diff --git a/backend/services/test_radio.py b/backend/services/test_radio.py deleted file mode 100644 index 37d47b3..0000000 --- a/backend/services/test_radio.py +++ /dev/null @@ -1,19 +0,0 @@ -import requests - -def test_openmhz(): - print("Testing OpenMHZ...") - res = requests.get("https://api.openmhz.com/systems") - if res.status_code == 200: - data = res.json() - print(f"OpenMHZ returned {len(data)} systems.") - print(f"Example: {data[0]['name']} ({data[0]['shortName']})") - else: - print(f"OpenMHZ Failed: {res.status_code}") - -def test_scanner_radio(): - print("Testing Scanner Radio...") - # Gordon Edwards app often uses something like this - # We will just try broadcastify public page scrape as a secondary fallback - pass - -test_openmhz() diff --git a/backend/services/test_rss.py b/backend/services/test_rss.py deleted file mode 100644 index 000c7b3..0000000 --- a/backend/services/test_rss.py +++ /dev/null @@ -1,55 +0,0 @@ -import feedparser -import requests -import re - -feeds = { - "NPR": "https://feeds.npr.org/1004/rss.xml", - "BBC": "http://feeds.bbci.co.uk/news/world/rss.xml" -} - -keyword_coords = { - "venezuela": (7.119, -66.589), "brazil": (-14.235, -51.925), "argentina": (-38.416, -63.616), - "colombia": (4.570, -74.297), "mexico": (23.634, -102.552), "united states": (38.907, -77.036), - " usa ": (38.907, -77.036), " us ": (38.907, -77.036), "washington": (38.907, -77.036), - "canada": (56.130, -106.346), "ukraine": (49.487, 31.272), "kyiv": (50.450, 30.523), - "russia": (61.524, 105.318), "moscow": (55.755, 37.617), "israel": (31.046, 34.851), - "gaza": (31.416, 34.333), "iran": (32.427, 53.688), "lebanon": (33.854, 35.862), - "syria": (34.802, 38.996), "yemen": (15.552, 48.516), "china": (35.861, 104.195), - "beijing": (39.904, 116.407), "taiwan": (23.697, 120.960), "north korea": (40.339, 127.510), - "south korea": (35.907, 127.766), "pyongyang": (39.039, 125.762), "seoul": (37.566, 126.978), - "japan": (36.204, 138.252), "afghanistan": (33.939, 67.709), "pakistan": (30.375, 69.345), - "india": (20.593, 78.962), " uk ": (55.378, -3.435), "london": (51.507, -0.127), - "france": (46.227, 2.213), "paris": (48.856, 2.352), "germany": (51.165, 10.451), - "berlin": (52.520, 13.405), "sudan": (12.862, 30.217), "congo": (-4.038, 21.758), - "south africa": (-30.559, 22.937), "nigeria": (9.082, 8.675), "egypt": (26.820, 30.802), - "zimbabwe": (-19.015, 29.154), "australia": (-25.274, 133.775), "middle east": (31.500, 34.800), - "europe": (48.800, 2.300), "africa": (0.000, 25.000), "america": (38.900, -77.000), - "south america": (-14.200, -51.900), "asia": (34.000, 100.000), - "california": (36.778, -119.417), "texas": (31.968, -99.901), "florida": (27.994, -81.760), - "new york": (40.712, -74.006), "virginia": (37.431, -78.656), - "british columbia": (53.726, -127.647), "ontario": (51.253, -85.323), "quebec": (52.939, -73.549), - "delhi": (28.704, 77.102), "new delhi": (28.613, 77.209), "mumbai": (19.076, 72.877), - "shanghai": (31.230, 121.473), "hong kong": (22.319, 114.169), "istanbul": (41.008, 28.978), - "dubai": (25.204, 55.270), "singapore": (1.352, 103.819) -} - -for name, url in feeds.items(): - r = requests.get(url) - feed = feedparser.parse(r.text) - for entry in feed.entries[:10]: - title = entry.get('title', '') - summary = entry.get('summary', '') - text = (title + " " + summary).lower() - padded_text = f" {text} " - - matched_kw = None - for kw, coords in keyword_coords.items(): - if kw.startswith(" ") or kw.endswith(" "): - if kw in padded_text: - matched_kw = kw - break - else: - if re.search(r'\b' + re.escape(kw) + r'\b', text): - matched_kw = kw - break - print(f"[{name}] {title}\n Matched: {matched_kw}\n Text: {text}\n") diff --git a/backend/services/test_scraper.py b/backend/services/test_scraper.py deleted file mode 100644 index bf9e938..0000000 --- a/backend/services/test_scraper.py +++ /dev/null @@ -1,67 +0,0 @@ -import requests -from bs4 import BeautifulSoup -import json - -def scrape_broadcastify_top(): - print("Scraping Broadcastify Top Feeds...") - headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' - } - - try: - # The top 50 feeds page provides a wealth of listening data - res = requests.get("https://www.broadcastify.com/listen/top", headers=headers, timeout=10) - if res.status_code != 200: - print(f"Failed HTTP {res.status_code}") - return [] - - soup = BeautifulSoup(res.text, 'html.parser') - - # The table of feeds is in a standard class - table = soup.find('table', {'class': 'btable'}) - if not table: - print("Could not find feeds table.") - return [] - - feeds = [] - rows = table.find_all('tr')[1:] # Skip header - - for row in rows: - cols = row.find_all('td') - if len(cols) >= 5: - # Top layout: [Listeners, Feed ID (hidden), Location, Feed Name, Category, Genre] - listeners_str = cols[0].text.strip().replace(',', '') - listeners = int(listeners_str) if listeners_str.isdigit() else 0 - - # The link is usually in the Feed Name column - link_tag = cols[2].find('a') - if not link_tag: - continue - - href = link_tag.get('href', '') - feed_id = href.split('/')[-1] if '/listen/feed/' in href else None - - if not feed_id: - continue - - location = cols[1].text.strip() - name = cols[2].text.strip() - - feeds.append({ - "id": feed_id, - "listeners": listeners, - "location": location, - "name": name, - "stream_url": f"https://broadcastify.cdnstream1.com/{feed_id}" - }) - - print(f"Successfully scraped {len(feeds)} top feeds.") - return feeds - - except Exception as e: - print(f"Scrape error: {e}") - return [] - -if __name__ == "__main__": - top_feeds = scrape_broadcastify_top() - print(json.dumps(top_feeds[:3], indent=2)) diff --git a/backend/test_adsb.py b/backend/test_adsb.py deleted file mode 100644 index c41cffc..0000000 --- a/backend/test_adsb.py +++ /dev/null @@ -1,59 +0,0 @@ -import requests -import time -import math -import random - -def test_fetch_and_triangulate(): - t0 = time.time() - url = "https://api.adsb.lol/v2/lat/39.8/lon/-98.5/dist/1000" - try: - r = requests.get(url, timeout=10) - data = r.json() - print(f"Downloaded in {time.time() - t0:.2f}s") - if "ac" in data: - sampled = data["ac"] - print("Flights:", len(sampled)) - else: - print("No 'ac' in response:", data) - - - # Load airports (mock for test) - airports = [{"lat": random.uniform(-90, 90), "lng": random.uniform(-180, 180), "iata": f"A{i}"} for i in range(4000)] - - t1 = time.time() - for f in sampled: - lat = f.get("lat") - lng = f.get("lon") - heading = f.get("track", 0) - if lat is None or lng is None: continue - - # Project 15 degrees (~1000 miles) backwards and forwards - dist_deg = 15.0 - h_rad = math.radians(heading) - dy = math.cos(h_rad) * dist_deg - dx = math.sin(h_rad) * dist_deg - cos_lat = max(0.2, math.cos(math.radians(lat))) - - origin_lat = lat - dy - origin_lng = lng - (dx / cos_lat) - - dest_lat = lat + dy - dest_lng = lng + (dx / cos_lat) - - # Find closest origin airport - best_o, min_o = None, float('inf') - for a in airports: - d = (a['lat'] - origin_lat)**2 + (a['lng'] - origin_lng)**2 - if d < min_o: min_o = d; best_o = a - - # Find closest dest airport - best_d, min_d = None, float('inf') - for a in airports: - d = (a['lat'] - dest_lat)**2 + (a['lng'] - dest_lng)**2 - if d < min_d: min_d = d; best_d = a - - print(f"Triangulated 500 flights against {len(airports)} airports in {time.time() - t1:.2f}s") - except Exception as e: - print("Error:", e) - -test_fetch_and_triangulate() diff --git a/backend/test_adsb_inner.py b/backend/test_adsb_inner.py deleted file mode 100644 index 882196d..0000000 --- a/backend/test_adsb_inner.py +++ /dev/null @@ -1,13 +0,0 @@ -from services.data_fetcher import fetch_airports, fetch_flights, cached_airports, latest_data - -fetch_airports() - -# We patch logger to see what happens inside fetch_flights -import logging -logging.basicConfig(level=logging.DEBUG) - -# let's run fetch_flights -fetch_flights() - -flights = latest_data.get('flights', []) -print(f"Total flights: {len(flights)}") diff --git a/backend/test_ais_proxy.py b/backend/test_ais_proxy.py deleted file mode 100644 index 23b7bd3..0000000 --- a/backend/test_ais_proxy.py +++ /dev/null @@ -1,45 +0,0 @@ -import json -import subprocess -import os -import time - -proxy_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ais_proxy.js") -API_KEY = "75cc39af03c9cc23c90e8a7b3c3bc2b2a507c5fb" - -print(f"Proxy script: {proxy_script}") -print(f"Exists: {os.path.exists(proxy_script)}") - -process = subprocess.Popen( - ['node', proxy_script, API_KEY], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, # Separate stderr! - text=True, - bufsize=1 -) - -print("Process started, reading stdout...") -count = 0 -start = time.time() -for line in iter(process.stdout.readline, ''): - line = line.strip() - if not line: - continue - try: - data = json.loads(line) - msg_type = data.get("MessageType", "?") - mmsi = data.get("MetaData", {}).get("MMSI", 0) - count += 1 - if count <= 5: - print(f" MSG {count}: type={msg_type} mmsi={mmsi}") - if count == 20: - elapsed = time.time() - start - print(f"\nReceived {count} messages in {elapsed:.1f}s — proxy is working!") - process.terminate() - break - except json.JSONDecodeError as e: - print(f" BAD JSON: {line[:100]}... err={e}") - -if count == 0: - # Check stderr - stderr_out = process.stderr.read() - print(f"Zero messages received. stderr: {stderr_out[:500]}") diff --git a/backend/test_ais_proxy2.py b/backend/test_ais_proxy2.py deleted file mode 100644 index e200f09..0000000 --- a/backend/test_ais_proxy2.py +++ /dev/null @@ -1,54 +0,0 @@ -import json -import subprocess -import os -import time -import sys - -proxy_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ais_proxy.js") -API_KEY = "75cc39af03c9cc23c90e8a7b3c3bc2b2a507c5fb" - -print(f"Proxy script: {proxy_script}") - -process = subprocess.Popen( - ['node', proxy_script, API_KEY], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - bufsize=1 -) - -import threading - -def read_stderr(): - for line in iter(process.stderr.readline, ''): - print(f"[STDERR] {line.strip()}", file=sys.stderr) - -t = threading.Thread(target=read_stderr, daemon=True) -t.start() - -print("Process started, reading stdout for 15 seconds...") -count = 0 -start = time.time() -while time.time() - start < 15: - line = process.stdout.readline() - if not line: - if process.poll() is not None: - print(f"Process exited with code {process.returncode}") - break - continue - line = line.strip() - if not line: - continue - try: - data = json.loads(line) - msg_type = data.get("MessageType", "?") - mmsi = data.get("MetaData", {}).get("MMSI", 0) - count += 1 - if count <= 5: - print(f" MSG {count}: type={msg_type} mmsi={mmsi}") - except json.JSONDecodeError as e: - print(f" BAD LINE: {line[:80]}...") - -elapsed = time.time() - start -print(f"\nTotal {count} messages in {elapsed:.1f}s") -process.terminate() diff --git a/backend/test_api.py b/backend/test_api.py deleted file mode 100644 index 82f87f9..0000000 --- a/backend/test_api.py +++ /dev/null @@ -1,13 +0,0 @@ -import requests -import traceback - -try: - print("Testing adsb.lol...") - r = requests.get("https://api.adsb.lol/v2/lat/39.8/lon/-98.5/dist/100", timeout=15) - print(f"Status: {r.status_code}") - d = r.json() - print(f"Aircraft: {len(d.get('ac', []))}") -except Exception as e: - print(f"Error type: {type(e).__name__}") - print(f"Error: {e}") - traceback.print_exc() diff --git a/backend/test_api_stats.py b/backend/test_api_stats.py deleted file mode 100644 index c8a3e77..0000000 --- a/backend/test_api_stats.py +++ /dev/null @@ -1,11 +0,0 @@ -import json -import urllib.request -import time - -time.sleep(5) -try: - data = urllib.request.urlopen('http://localhost:8000/api/live-data').read() - d = json.loads(data) - print(f"News: {len(d.get('news', []))} | Earthquakes: {len(d.get('earthquakes', []))} | Satellites: {len(d.get('satellites', []))} | CCTV: {len(d.get('cctv', []))}") -except Exception as e: - print(f"Error fetching API: {e}") diff --git a/backend/test_batch.py b/backend/test_batch.py deleted file mode 100644 index 3308e00..0000000 --- a/backend/test_batch.py +++ /dev/null @@ -1,56 +0,0 @@ -import requests -import json - -# Step 1: Fetch some real flights from adsb.lol -print("Fetching real flights from adsb.lol...") -r = requests.get("https://api.adsb.lol/v2/lat/39.8/lon/-98.5/dist/250", timeout=10) -data = r.json() -ac = data.get("ac", []) -print("Got", len(ac), "aircraft") - -# Step 2: Build a batch of real callsigns -planes = [] -for f in ac[:20]: # Just 20 real flights - cs = str(f.get("flight", "")).strip() - lat = f.get("lat") - lon = f.get("lon") - if cs and lat and lon: - planes.append({"callsign": cs, "lat": lat, "lng": lon}) - -print("Built batch of", len(planes), "planes") -print("Sample plane:", json.dumps(planes[0]) if planes else "NONE") - -# Step 3: Test routeset with real data -if planes: - payload = {"planes": planes} - print("Payload size:", len(json.dumps(payload)), "bytes") - r2 = requests.post("https://api.adsb.lol/api/0/routeset", json=payload, timeout=15) - print("Routeset HTTP:", r2.status_code) - if r2.status_code == 200: - result = r2.json() - print("Response type:", type(result).__name__) - print("Routes found:", len(result) if isinstance(result, list) else "dict") - if isinstance(result, list) and len(result) > 0: - print("First route:", json.dumps(result[0], indent=2)) - else: - print("Error body:", r2.text[:500]) - -# Step 4: Test with bigger batch -print("\n--- Testing with 100 real flights ---") -planes100 = [] -for f in ac[:120]: - cs = str(f.get("flight", "")).strip() - lat = f.get("lat") - lon = f.get("lon") - if cs and lat and lon: - planes100.append({"callsign": cs, "lat": lat, "lng": lon}) -planes100 = planes100[:100] - -print("Built batch of", len(planes100), "planes") -r3 = requests.post("https://api.adsb.lol/api/0/routeset", json={"planes": planes100}, timeout=15) -print("Routeset HTTP:", r3.status_code) -if r3.status_code == 200: - result3 = r3.json() - print("Routes found:", len(result3) if isinstance(result3, list) else "dict") -else: - print("Error body:", r3.text[:500]) diff --git a/backend/test_cctv.py b/backend/test_cctv.py deleted file mode 100644 index 87f9b92..0000000 --- a/backend/test_cctv.py +++ /dev/null @@ -1,10 +0,0 @@ -from services.cctv_pipeline import init_db, TFLJamCamIngestor, LTASingaporeIngestor - -init_db() -print("Initialized DB") - -tfl = TFLJamCamIngestor() -print(f"TFL Cameras: {len(tfl.fetch_data())}") - -nyc = LTASingaporeIngestor() -print(f"SGP Cameras: {len(nyc.fetch_data())}") diff --git a/backend/test_cctv_endpoints.py b/backend/test_cctv_endpoints.py deleted file mode 100644 index 2749617..0000000 --- a/backend/test_cctv_endpoints.py +++ /dev/null @@ -1,24 +0,0 @@ -import requests - -try: - print('Testing Seattle SDOT...') - r_sea = requests.get('https://data.seattle.gov/resource/65fc-btcc.json?$limit=5', headers={'X-App-Token': 'f2jdDBw5JMXPFOQyk64SKlPkn'}) - print(r_sea.status_code) - try: - print(r_sea.json()[0]) - except: - pass -except: - pass - -try: - print('Testing NYC 511...') - r_nyc = requests.get('https://webcams.nyctmc.org/api/cameras', timeout=5) - print(r_nyc.status_code) - try: - print(len(r_nyc.json())) - print(r_nyc.json()[0]) - except: - pass -except: - pass diff --git a/backend/test_counts.py b/backend/test_counts.py deleted file mode 100644 index feb4841..0000000 --- a/backend/test_counts.py +++ /dev/null @@ -1,10 +0,0 @@ -import json, urllib.request - -data = json.loads(urllib.request.urlopen('http://localhost:8000/api/live-data').read()) -print(f"Commercial flights: {len(data.get('commercial_flights', []))}") -print(f"Private flights: {len(data.get('private_flights', []))}") -print(f"Private jets: {len(data.get('private_jets', []))}") -print(f"Military flights: {len(data.get('military_flights', []))}") -print(f"Tracked flights: {len(data.get('tracked_flights', []))}") -print(f"Ships: {len(data.get('ships', []))}") -print(f"CCTV: {len(data.get('cctv', []))}") diff --git a/backend/test_debug_api.py b/backend/test_debug_api.py deleted file mode 100644 index ecc17c1..0000000 --- a/backend/test_debug_api.py +++ /dev/null @@ -1,38 +0,0 @@ -import json -import urllib.request - -try: - data = json.loads(urllib.request.urlopen('http://localhost:8000/api/live-data').read()) - - # Tracked flights - tracked = data.get('tracked_flights', []) - print(f"=== TRACKED FLIGHTS: {len(tracked)} ===") - if tracked: - colors = {} - for t in tracked: - c = t.get('alert_color', 'NONE') - colors[c] = colors.get(c, 0) + 1 - print(f" Colors: {colors}") - print(f" Sample: {json.dumps(tracked[0], indent=2)[:500]}") - - # Ships - ships = data.get('ships', []) - print(f"\n=== SHIPS: {len(ships)} ===") - types = {} - for s in ships: - t = s.get('type', 'unknown') - types[t] = types.get(t, 0) + 1 - print(f" Types: {types}") - if ships: - print(f" Sample: {json.dumps(ships[0], indent=2)[:300]}") - - # News - news = data.get('news', []) - print(f"\n=== NEWS: {len(news)} ===") - - # Earthquakes - quakes = data.get('earthquakes', []) - print(f"=== EARTHQUAKES: {len(quakes)} ===") - -except Exception as e: - print(f"Error: {e}") diff --git a/backend/test_final.py b/backend/test_final.py deleted file mode 100644 index 1c9b7b6..0000000 --- a/backend/test_final.py +++ /dev/null @@ -1,23 +0,0 @@ -import json -import urllib.request - -try: - data = json.loads(urllib.request.urlopen('http://localhost:8000/api/live-data').read()) - - tracked = data.get('tracked_flights', []) - colors = {} - for t in tracked: - c = t.get('alert_color', 'NONE') - colors[c] = colors.get(c, 0) + 1 - print(f"TRACKED FLIGHTS: {len(tracked)} | Colors: {colors}") - - ships = data.get('ships', []) - types = {} - for s in ships: - t = s.get('type', 'unknown') - types[t] = types.get(t, 0) + 1 - print(f"SHIPS: {len(ships)} | Types: {types}") - - print(f"NEWS: {len(data.get('news', []))} | EARTHQUAKES: {len(data.get('earthquakes', []))} | CCTV: {len(data.get('cctv', []))}") -except Exception as e: - print(f"Error: {e}") diff --git a/backend/test_nyc2.py b/backend/test_nyc2.py deleted file mode 100644 index a78f91b..0000000 --- a/backend/test_nyc2.py +++ /dev/null @@ -1,10 +0,0 @@ -import requests, json - -url = "https://api.us.socrata.com/api/catalog/v1?domains=data.cityofnewyork.us&q=camera" -try: - r = requests.get(url) - res = r.json().get('results', []) - for d in res: - print(f"{d['resource']['id']} - {d['resource']['name']}") -except Exception as e: - print(e) diff --git a/backend/test_origin.py b/backend/test_origin.py deleted file mode 100644 index 311559f..0000000 --- a/backend/test_origin.py +++ /dev/null @@ -1,36 +0,0 @@ -import json, urllib.request - -data = json.loads(urllib.request.urlopen('http://localhost:8000/api/live-data').read()) - -# Check trail data -comm = data.get('commercial_flights', []) -mil = data.get('military_flights', []) -tracked = data.get('tracked_flights', []) -pvt = data.get('private_flights', []) - -# Count flights with trails -comm_trails = [f for f in comm if f.get('trail') and len(f['trail']) > 0] -mil_trails = [f for f in mil if f.get('trail') and len(f['trail']) > 0] -tracked_trails = [f for f in tracked if f.get('trail') and len(f['trail']) > 0] -pvt_trails = [f for f in pvt if f.get('trail') and len(f['trail']) > 0] - -print(f"Commercial: {len(comm)} total, {len(comm_trails)} with trails") -print(f"Military: {len(mil)} total, {len(mil_trails)} with trails") -print(f"Tracked: {len(tracked)} total, {len(tracked_trails)} with trails") -print(f"Private: {len(pvt)} total, {len(pvt_trails)} with trails") - -# Show a sample trail -if mil_trails: - f = mil_trails[0] - print(f"\nSample trail ({f['callsign']}):") - print(f" Points: {len(f['trail'])}") - if f['trail']: - print(f" First: {f['trail'][0]}") - print(f" Last: {f['trail'][-1]}") - -# Check for grounded planes -grounded = [f for f in comm if f.get('alt', 999) <= 500 and f.get('speed_knots', 999) < 30] -print(f"\nGrounded commercial: {len(grounded)}") -if grounded: - g = grounded[0] - print(f" Example: {g['callsign']} alt={g.get('alt')} speed={g.get('speed_knots')}") diff --git a/backend/test_osm_db.py b/backend/test_osm_db.py deleted file mode 100644 index f794def..0000000 --- a/backend/test_osm_db.py +++ /dev/null @@ -1,13 +0,0 @@ -import sqlite3 - -try: - conn = sqlite3.connect('cctv.db') - conn.row_factory = sqlite3.Row - cur = conn.cursor() - cur.execute("SELECT source_agency, COUNT(*) as count FROM cameras WHERE id LIKE 'OSM-%' GROUP BY source_agency") - rows = cur.fetchall() - print('OSM Cameras by City:') - for r in rows: - print(f"{r['source_agency']}: {r['count']}") -except Exception as e: - print('DB Error:', e) diff --git a/backend/test_ships.py b/backend/test_ships.py deleted file mode 100644 index 6627547..0000000 --- a/backend/test_ships.py +++ /dev/null @@ -1,12 +0,0 @@ -import json -import urllib.request -import time - -time.sleep(5) -try: - data = urllib.request.urlopen('http://localhost:8000/api/live-data').read() - d = json.loads(data) - ships = d.get('ships', []) - print(f"Ships: {len(ships)}") -except Exception as e: - print(f"Error fetching API: {e}") diff --git a/backend/test_socrata.py b/backend/test_socrata.py deleted file mode 100644 index 5951bef..0000000 --- a/backend/test_socrata.py +++ /dev/null @@ -1,13 +0,0 @@ -import requests, json - -print("Searching Socrata NYC/Seattle Cameras...") -try: - url = "https://api.us.socrata.com/api/catalog/v1?q=traffic cameras&limit=100" - r = requests.get(url) - res = r.json().get('results', []) - for d in res: - domain = d['metadata']['domain'].lower() - if 'seattle' in domain or 'newyork' in domain or 'nyc' in domain: - print(f"{d['resource']['id']} - {d['resource']['name']} ({domain})") -except Exception as e: - print(e) diff --git a/backend/test_trace.py b/backend/test_trace.py deleted file mode 100644 index 21d9bed..0000000 --- a/backend/test_trace.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Test trace endpoints with explicit output.""" -import json, subprocess - -hex_code = "a34bac" # DOJ166 - -from datetime import datetime, timezone -now = datetime.now(timezone.utc) -date_str = now.strftime("%Y/%m/%d") -hex_prefix = hex_code[-2:] - -# Test 1: adsb.fi trace_full -url1 = f"https://globe.adsb.fi/data/traces/{date_str}/{hex_prefix}/trace_full_{hex_code}.json" -print(f"URL1: {url1}") -r = subprocess.run(["curl", "-s", "--max-time", "10", url1], capture_output=True, text=True, timeout=15) -if r.stdout.strip().startswith("{"): - data = json.loads(r.stdout) - print(f"SUCCESS! Keys: {list(data.keys())}") - if 'trace' in data: - pts = data['trace'] - print(f"Trace points: {len(pts)}") - if pts: - print(f"FIRST (takeoff): {pts[0]}") - print(f"LAST (now): {pts[-1]}") -else: - print(f"Not JSON (first 100 chars): {r.stdout[:100]}") - # That response was behind cloudflare, try adsb.lol instead - -# Test 2: adsb.lol hex lookup -url2 = f"https://api.adsb.lol/v2/hex/{hex_code}" -print(f"\nURL2: {url2}") -r2 = subprocess.run(["curl", "-s", "--max-time", "10", url2], capture_output=True, text=True, timeout=15) -if r2.stdout.strip().startswith("{"): - data = json.loads(r2.stdout) - if 'ac' in data and data['ac']: - ac = data['ac'][0] - keys = sorted(ac.keys()) - print(f"All keys ({len(keys)}): {keys}") -else: - print(f"Not JSON: {r2.stdout[:100]}") - -# Test 3: Try adsb.lol trace -url3 = f"https://api.adsb.lol/trace/{hex_code}" -print(f"\nURL3: {url3}") -r3 = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "10", url3], capture_output=True, text=True, timeout=15) -print(f"HTTP status: {r3.stdout}") - -# Test 4: Try globe.adsb.lol format -url4 = f"https://globe.adsb.lol/data/traces/{date_str}/{hex_prefix}/trace_full_{hex_code}.json" -print(f"\nURL4: {url4}") -r4 = subprocess.run(["curl", "-s", "--max-time", "10", url4], capture_output=True, text=True, timeout=15) -if r4.stdout.strip().startswith("{"): - data = json.loads(r4.stdout) - print(f"SUCCESS! Keys: {list(data.keys())}") - if 'trace' in data: - pts = data['trace'] - print(f"Trace points: {len(pts)}") - if pts: - print(f"FIRST (takeoff): {pts[0]}") - print(f"LAST (now): {pts[-1]}") -else: - print(f"Response: {r4.stdout[:150]}") diff --git a/backend/test_ws.py b/backend/test_ws.py deleted file mode 100644 index d9dee1e..0000000 --- a/backend/test_ws.py +++ /dev/null @@ -1,8 +0,0 @@ -import asyncio, websockets -async def main(): - try: - async with websockets.connect('wss://stream.aisstream.io/v0/stream') as ws: - print('Connected to AIS Stream!') - except Exception as e: - print(f"Error: {e}") -asyncio.run(main()) diff --git a/clean_zip.py b/clean_zip.py deleted file mode 100644 index d4766bc..0000000 --- a/clean_zip.py +++ /dev/null @@ -1,37 +0,0 @@ -import os -import zipfile - -zip_name = 'ShadowBroker_v0.3.zip' - -if os.path.exists(zip_name): - try: - os.remove(zip_name) - except Exception as e: - print(f"Failed to delete old zip: {e}") - -def add_dir(zipf, dir_path, excludes): - for root, dirs, files in os.walk(dir_path): - dirs[:] = [d for d in dirs if d not in excludes] - for f in files: - file_path = os.path.join(root, f) - zipf.write(file_path, arcname=file_path) - -try: - with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf: - print("Zipping backend...") - add_dir(zipf, 'backend', {'venv', '__pycache__'}) - - print("Zipping frontend...") - add_dir(zipf, 'frontend', {'node_modules', '.next'}) - - print("Zipping root files...") - zipf.write('docker-compose.yml') - zipf.write('start.bat') - zipf.write('start.sh') - zipf.write('README.md') - - final_size = os.path.getsize(zip_name) / (1024 * 1024) - print(f"\n✅ SUCCESS! Created {zip_name}. Final size: {final_size:.2f} MB") - -except Exception as e: - print(f"\n❌ ERROR creating zip: {e}") diff --git a/frontend/README.md b/frontend/README.md index e215bc4..641a7b1 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,36 +1,51 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# ShadowBroker Frontend -## Getting Started +Next.js 16 dashboard with MapLibre GL, Cesium, and Framer Motion. -First, run the development server: +## Development ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +npm install +npm run dev # http://localhost:3000 ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +## API URL Configuration -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +The frontend needs to reach the backend (default port `8000`). Resolution order: -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +1. **`NEXT_PUBLIC_API_URL`** env var — if set, used as-is (build-time, baked by Next.js) +2. **Server-side (SSR)** — falls back to `http://localhost:8000` +3. **Client-side (browser)** — auto-detects using `window.location.hostname:8000` -## Learn More +### Common scenarios -To learn more about Next.js, take a look at the following resources: +| Scenario | Action needed | +|----------|---------------| +| Local dev (`localhost:3000` + `localhost:8000`) | None — auto-detected | +| LAN access (`192.168.x.x:3000`) | None — auto-detected from browser hostname | +| Public deploy (same host, port 8000) | None — auto-detected | +| Backend on different port (e.g. `9096`) | Set `NEXT_PUBLIC_API_URL=http://host:9096` before build | +| Backend on different host | Set `NEXT_PUBLIC_API_URL=http://backend-host:8000` before build | +| Behind reverse proxy (e.g. `/api` path) | Set `NEXT_PUBLIC_API_URL=https://yourdomain.com` before build | -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +### Setting the variable -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +```bash +# Shell (Linux/macOS) +NEXT_PUBLIC_API_URL=http://myserver:8000 npm run build -## Deploy on Vercel +# PowerShell (Windows) +$env:NEXT_PUBLIC_API_URL="http://myserver:8000"; npm run build -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +# Docker Compose (set in .env file next to docker-compose.yml) +NEXT_PUBLIC_API_URL=http://myserver:8000 +``` -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +> **Note:** This is a build-time variable. Changing it requires rebuilding the frontend. + +## Theming + +Dark mode is the default. A light/dark toggle is available in the left panel toolbar. +Theme preference is persisted in `localStorage` as `sb-theme` and applied via +`data-theme` attribute on ``. CSS variables in `globals.css` define all +structural colors for both themes. diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 5599d0c..ca3473d 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1,8 +1,40 @@ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; + --background: #000000; + --foreground: #ededed; + --bg-primary: #000000; + --bg-secondary: rgb(17, 24, 39); + --bg-tertiary: rgb(31, 41, 55); + --bg-panel: rgba(17, 24, 39, 0.8); + --border-primary: rgb(55, 65, 81); + --border-secondary: rgb(75, 85, 99); + --text-primary: rgb(243, 244, 246); + --text-secondary: rgb(156, 163, 175); + --text-muted: rgb(107, 114, 128); + --text-heading: rgb(236, 254, 255); + --hover-accent: rgba(8, 51, 68, 0.2); + --scrollbar-thumb: rgba(100, 116, 139, 0.3); + --scrollbar-thumb-hover: rgba(100, 116, 139, 0.5); +} + +/* Light theme: only the map basemap changes — UI stays dark */ +[data-theme="light"] { + --background: #000000; + --foreground: #ededed; + --bg-primary: #000000; + --bg-secondary: rgb(17, 24, 39); + --bg-tertiary: rgb(31, 41, 55); + --bg-panel: rgba(17, 24, 39, 0.8); + --border-primary: rgb(55, 65, 81); + --border-secondary: rgb(75, 85, 99); + --text-primary: rgb(243, 244, 246); + --text-secondary: rgb(156, 163, 175); + --text-muted: rgb(107, 114, 128); + --text-heading: rgb(236, 254, 255); + --hover-accent: rgba(8, 51, 68, 0.2); + --scrollbar-thumb: rgba(100, 116, 139, 0.3); + --scrollbar-thumb-hover: rgba(100, 116, 139, 0.5); } @theme inline { @@ -12,13 +44,6 @@ --font-mono: var(--font-geist-mono); } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - body { background: var(--background); color: var(--foreground); @@ -35,12 +60,12 @@ body { } .styled-scrollbar::-webkit-scrollbar-thumb { - background: rgba(100, 116, 139, 0.3); + background: var(--scrollbar-thumb); border-radius: 10px; } .styled-scrollbar::-webkit-scrollbar-thumb:hover { - background: rgba(100, 116, 139, 0.5); + background: var(--scrollbar-thumb-hover); } .styled-scrollbar { diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index a014eb8..612e10d 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; +import { ThemeProvider } from "@/lib/ThemeContext"; import "./globals.css"; const geistSans = Geist({ @@ -29,10 +30,10 @@ export default function RootLayout({ - {children} + {children} ); diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index b12fc01..34e75b3 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -16,10 +16,105 @@ import MapLegend from "@/components/MapLegend"; import ScaleBar from "@/components/ScaleBar"; import ErrorBoundary from "@/components/ErrorBoundary"; import OnboardingModal, { useOnboarding } from "@/components/OnboardingModal"; +import ChangelogModal, { useChangelog } from "@/components/ChangelogModal"; // Use dynamic loads for Maplibre to avoid SSR window is not defined errors const MaplibreViewer = dynamic(() => import('@/components/MaplibreViewer'), { ssr: false }); +/* ── LOCATE BAR ── coordinate / place-name search above bottom status bar ── */ +function LocateBar({ onLocate }: { onLocate: (lat: number, lng: number) => void }) { + const [open, setOpen] = useState(false); + const [value, setValue] = useState(''); + const [results, setResults] = useState<{ label: string; lat: number; lng: number }[]>([]); + const [loading, setLoading] = useState(false); + const inputRef = useRef(null); + const timerRef = useRef>(); + + useEffect(() => { if (open) inputRef.current?.focus(); }, [open]); + + // Parse raw coordinate input: "31.8, 34.8" or "31.8 34.8" or "-12.3, 45.6" + const parseCoords = (s: string): { lat: number; lng: number } | null => { + const m = s.trim().match(/^([+-]?\d+\.?\d*)[,\s]+([+-]?\d+\.?\d*)$/); + if (!m) return null; + const lat = parseFloat(m[1]), lng = parseFloat(m[2]); + if (lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) return { lat, lng }; + return null; + }; + + const handleSearch = async (q: string) => { + setValue(q); + // Check for raw coordinates first + const coords = parseCoords(q); + if (coords) { + setResults([{ label: `${coords.lat.toFixed(4)}, ${coords.lng.toFixed(4)}`, ...coords }]); + return; + } + // Geocode with Nominatim (debounced) + clearTimeout(timerRef.current); + if (q.trim().length < 2) { setResults([]); return; } + timerRef.current = setTimeout(async () => { + setLoading(true); + try { + const res = await fetch(`https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(q)}&format=json&limit=5`, { + headers: { 'Accept-Language': 'en' }, + }); + const data = await res.json(); + setResults(data.map((r: any) => ({ label: r.display_name, lat: parseFloat(r.lat), lng: parseFloat(r.lon) }))); + } catch { setResults([]); } + setLoading(false); + }, 350); + }; + + const handleSelect = (r: { lat: number; lng: number }) => { + onLocate(r.lat, r.lng); + setOpen(false); + setValue(''); + setResults([]); + }; + + if (!open) { + return ( + + ); + } + + return ( +
+
+ + handleSearch(e.target.value)} + onKeyDown={(e) => { if (e.key === 'Escape') { setOpen(false); setValue(''); setResults([]); } if (e.key === 'Enter' && results.length > 0) handleSelect(results[0]); }} + placeholder="Enter coordinates (31.8, 34.8) or place name..." + className="flex-1 bg-transparent text-[10px] text-[var(--text-primary)] font-mono tracking-wider outline-none placeholder:text-[var(--text-muted)]" + /> + {loading &&
} + +
+ {results.length > 0 && ( +
+ {results.map((r, i) => ( + + ))} +
+ )} +
+ ); +} + export default function Dashboard() { const dataRef = useRef({}); const [dataVersion, setDataVersion] = useState(0); @@ -48,19 +143,33 @@ export default function Dashboard() { global_incidents: true, day_night: true, gps_jamming: true, + gibs_imagery: false, + highres_satellite: false, + kiwisdr: false, }); + // NASA GIBS satellite imagery state + const [gibsDate, setGibsDate] = useState(() => { + const d = new Date(); + d.setDate(d.getDate() - 1); + return d.toISOString().slice(0, 10); + }); + const [gibsOpacity, setGibsOpacity] = useState(0.6); + const [effects, setEffects] = useState({ bloom: true, }); const [activeStyle, setActiveStyle] = useState('DEFAULT'); - const stylesList = ['DEFAULT', 'FLIR', 'NVG', 'CRT']; + const stylesList = ['DEFAULT', 'SATELLITE', 'FLIR', 'NVG', 'CRT']; const cycleStyle = () => { setActiveStyle((prev) => { const idx = stylesList.indexOf(prev); - return stylesList[(idx + 1) % stylesList.length]; + const next = stylesList[(idx + 1) % stylesList.length]; + // Auto-toggle High-Res Satellite layer with SATELLITE style + setActiveLayers((l: any) => ({ ...l, highres_satellite: next === 'SATELLITE' })); + return next; }); }; @@ -79,6 +188,7 @@ export default function Dashboard() { // Onboarding & connection status const { showOnboarding, setShowOnboarding } = useOnboarding(); + const { showChangelog, setShowChangelog } = useChangelog(); const [backendStatus, setBackendStatus] = useState<'connecting' | 'connected' | 'disconnected'>('connecting'); const geocodeCache = useRef>(new Map()); const geocodeTimer = useRef | null>(null); @@ -152,11 +262,19 @@ export default function Dashboard() { setRegionDossierLoading(true); setRegionDossier(null); try { - const res = await fetch(`${API_BASE}/api/region-dossier?lat=${coords.lat}&lng=${coords.lng}`); - if (res.ok) { - const data = await res.json(); - setRegionDossier(data); + const [dossierRes, sentinelRes] = await Promise.allSettled([ + fetch(`${API_BASE}/api/region-dossier?lat=${coords.lat}&lng=${coords.lng}`), + fetch(`${API_BASE}/api/sentinel2/search?lat=${coords.lat}&lng=${coords.lng}`), + ]); + let dossierData: any = {}; + if (dossierRes.status === 'fulfilled' && dossierRes.value.ok) { + dossierData = await dossierRes.value.json(); } + let sentinelData = null; + if (sentinelRes.status === 'fulfilled' && sentinelRes.value.ok) { + sentinelData = await sentinelRes.value.json(); + } + setRegionDossier({ ...dossierData, sentinel2: sentinelData }); } catch (e) { console.error("Failed to fetch region dossier", e); } finally { @@ -228,7 +346,7 @@ export default function Dashboard() { }, []); return ( -
+
{/* MAPLIBRE WEBGL OVERLAY */} @@ -240,6 +358,8 @@ export default function Dashboard() { onEntityClick={setSelectedEntity} selectedEntity={selectedEntity} flyToLocation={flyToLocation} + gibsDate={gibsDate} + gibsOpacity={gibsOpacity} isEavesdropping={isEavesdropping} onEavesdropClick={setEavesdropLocation} onCameraMove={setCameraCenter} @@ -274,10 +394,10 @@ export default function Dashboard() {
-

+

S H A D O W B R O K E R

- GLOBAL THREAT INTERCEPT + GLOBAL THREAT INTERCEPT
@@ -287,7 +407,7 @@ export default function Dashboard() { {/* SYSTEM METRICS TOP RIGHT */} -
+
RTX
VSR
@@ -295,7 +415,7 @@ export default function Dashboard() { {/* LEFT HUD CONTAINER */}
{/* LEFT PANEL - DATA LAYERS */} - setSettingsOpen(true)} onLegendClick={() => setLegendOpen(true)} /> + setSettingsOpen(true)} onLegendClick={() => setLegendOpen(true)} gibsDate={gibsDate} setGibsDate={setGibsDate} gibsOpacity={gibsOpacity} setGibsOpacity={setGibsOpacity} /> {/* LEFT BOTTOM - DISPLAY CONFIG */} @@ -335,6 +455,7 @@ export default function Dashboard() { setIsEavesdropping={setIsEavesdropping} eavesdropLocation={eavesdropLocation} cameraCenter={cameraCenter} + selectedEntity={selectedEntity} />
@@ -354,37 +475,40 @@ export default function Dashboard() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 1, duration: 1 }} - className="absolute bottom-6 left-1/2 -translate-x-1/2 z-[200] pointer-events-auto" + className="absolute bottom-6 left-1/2 -translate-x-1/2 z-[200] pointer-events-auto flex flex-col items-center gap-2" > + {/* LOCATE BAR — search by coordinates or place name */} + setFlyToLocation({ lat, lng, ts: Date.now() })} /> +
{/* Coordinates */}
-
COORDINATES
+
COORDINATES
{mouseCoords ? `${mouseCoords.lat.toFixed(4)}, ${mouseCoords.lng.toFixed(4)}` : '0.0000, 0.0000'}
{/* Divider */} -
+
{/* Location name */}
-
LOCATION
-
+
LOCATION
+
{locationLabel || 'Hover over map...'}
{/* Divider */} -
+
{/* Style preset (compact) */}
-
STYLE
+
STYLE
{activeStyle}
@@ -396,7 +520,7 @@ export default function Dashboard() { {!uiVisible && ( @@ -441,6 +565,11 @@ export default function Dashboard() { /> )} + {/* v0.4 CHANGELOG MODAL — shows once per version after onboarding */} + {!showOnboarding && showChangelog && ( + setShowChangelog(false)} /> + )} + {/* BACKEND DISCONNECTED BANNER */} {backendStatus === 'disconnected' && (
diff --git a/frontend/src/components/AdvancedFilterModal.tsx b/frontend/src/components/AdvancedFilterModal.tsx index efa380c..696095d 100644 --- a/frontend/src/components/AdvancedFilterModal.tsx +++ b/frontend/src/components/AdvancedFilterModal.tsx @@ -171,16 +171,16 @@ export default function AdvancedFilterModal({ animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.92 }} transition={{ duration: 0.2 }} - className={`bg-[#0a0e14]/95 backdrop-blur-xl border ${c.border} rounded-xl shadow-[0_8px_60px_rgba(0,0,0,0.8)] flex flex-col font-mono overflow-hidden`} + className={`bg-[var(--bg-secondary)]/95 backdrop-blur-xl border ${c.border} rounded-xl shadow-[0_8px_60px_rgba(0,0,0,0.3)] flex flex-col font-mono overflow-hidden`} style={{ maxHeight: '70vh' }} > {/* ── Title Bar (Draggable) ── */}
- + {icon} {title} {totalSelected > 0 && ( @@ -189,14 +189,14 @@ export default function AdvancedFilterModal({ )}
-
{/* ── Tab Bar (for multi-field categories) ── */} {fields.length > 1 && ( -
+
{fields.map(field => { const isActive = activeTab === field.key; const count = draft[field.key]?.size || 0; @@ -257,7 +257,7 @@ export default function AdvancedFilterModal({ value={searchTerms[activeTab] || ''} onChange={(e) => setSearchTerms(prev => ({ ...prev, [activeTab]: e.target.value }))} placeholder={`Search ${activeField?.label.toLowerCase() || ''}...`} - className={`w-full bg-black/50 border border-gray-700/70 rounded-lg text-[11px] text-gray-300 pl-8 pr-8 py-2 font-mono tracking-wide focus:outline-none focus:${c.border} focus:ring-1 ${c.ring} placeholder-gray-600 transition-all`} + className={`w-full bg-[var(--bg-primary)]/50 border border-[var(--border-primary)]/70 rounded-lg text-[11px] text-[var(--text-secondary)] pl-8 pr-8 py-2 font-mono tracking-wide focus:outline-none focus:${c.border} focus:ring-1 ${c.ring} placeholder-[var(--text-muted)] transition-all`} autoFocus /> {searchTerms[activeTab] && ( @@ -270,10 +270,10 @@ export default function AdvancedFilterModal({ )}
- + {filteredOptions.length} AVAILABLE - + {draft[activeTab]?.size || 0} SELECTED
@@ -282,7 +282,7 @@ export default function AdvancedFilterModal({ {/* ── Scrollable Checkbox List ── */}
{filteredOptions.length === 0 ? ( -
+
NO MATCHING RESULTS
) : ( @@ -295,13 +295,13 @@ export default function AdvancedFilterModal({ onClick={() => toggleItem(activeTab, option)} className={`flex items-center gap-2.5 px-3 py-1.5 rounded-md text-left transition-all group ${isChecked ? `${c.bg} ${c.text}` - : `text-gray-400 hover:bg-gray-800/50 hover:text-gray-200` + : `text-[var(--text-secondary)] hover:bg-[var(--bg-tertiary)]/50 hover:text-[var(--text-primary)]` }`} > {/* Checkbox */}
{isChecked && }
@@ -316,7 +316,7 @@ export default function AdvancedFilterModal({
{/* ── Footer ── */} -
+
diff --git a/frontend/src/components/ChangelogModal.tsx b/frontend/src/components/ChangelogModal.tsx new file mode 100644 index 0000000..fd2dbb1 --- /dev/null +++ b/frontend/src/components/ChangelogModal.tsx @@ -0,0 +1,174 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { X, Satellite, Radio, MapPin, Image, Layers, Bug } from "lucide-react"; + +const CURRENT_VERSION = "0.4"; +const STORAGE_KEY = `shadowbroker_changelog_v${CURRENT_VERSION}`; + +const NEW_FEATURES = [ + { + icon: , + title: "NASA GIBS Satellite Imagery", + desc: "Daily MODIS Terra true-color imagery with 30-day time slider, play/pause animation, and opacity control.", + color: "cyan", + }, + { + icon: , + title: "High-Res Satellite (Esri)", + desc: "Sub-meter resolution imagery — zoom into buildings and terrain. Toggle in Data Layers or cycle to SATELLITE style.", + color: "green", + }, + { + icon: , + title: "KiwiSDR Radio Receivers", + desc: "500+ public SDR receivers plotted worldwide. Click any node to open a live radio tuner directly in the SIGINT panel.", + color: "amber", + }, + { + icon: , + title: "Sentinel-2 Intel Card", + desc: "Right-click anywhere — a floating intel card shows the latest Sentinel-2 satellite photo with capture date and cloud cover. Click to open full resolution.", + color: "blue", + }, + { + icon: , + title: "LOCATE Bar", + desc: "New search bar above coordinates — enter coordinates (31.8, 34.8) or place names (Tehran, Strait of Hormuz) to fly directly there.", + color: "purple", + }, + { + icon: , + title: "SATELLITE Style Preset", + desc: "STYLE button now cycles: DEFAULT → SATELLITE → FLIR → NVG → CRT. SATELLITE auto-enables high-res imagery.", + color: "cyan", + }, +]; + +const BUG_FIXES = [ + "Satellite imagery renders below all data icons — flights, ships, markers always visible on top", + "Sentinel-2 click now opens the actual high-res PNG image directly in browser", + "Light/dark theme fixed — UI stays dark, only the map basemap switches", +]; + +export function useChangelog() { + const [show, setShow] = useState(false); + useEffect(() => { + const seen = localStorage.getItem(STORAGE_KEY); + if (!seen) setShow(true); + }, []); + return { showChangelog: show, setShowChangelog: setShow }; +} + +interface ChangelogModalProps { + onClose: () => void; +} + +const ChangelogModal = React.memo(function ChangelogModal({ onClose }: ChangelogModalProps) { + const handleDismiss = () => { + localStorage.setItem(STORAGE_KEY, "true"); + onClose(); + }; + + return ( + + + +
e.stopPropagation()} + > + {/* Header */} +
+
+
+
+
+ v{CURRENT_VERSION} +
+

+ WHAT'S NEW +

+
+

+ SHADOWBROKER INTELLIGENCE PLATFORM UPDATE +

+
+ +
+
+ + {/* Content */} +
+ {/* New Features */} +
+
+
+ NEW CAPABILITIES +
+
+ {NEW_FEATURES.map((f) => ( +
+
{f.icon}
+
+
{f.title}
+
{f.desc}
+
+
+ ))} +
+
+ + {/* Bug Fixes */} +
+
+ + FIXES & IMPROVEMENTS +
+
+ {BUG_FIXES.map((fix, i) => ( +
+ + + {fix} +
+ ))} +
+
+
+ + {/* Footer */} +
+ +
+
+ + + ); +}); + +export default ChangelogModal; diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx index e0cbdc6..5075707 100644 --- a/frontend/src/components/ErrorBoundary.tsx +++ b/frontend/src/components/ErrorBoundary.tsx @@ -34,7 +34,7 @@ class ErrorBoundary extends Component {
⚠ SYSTEM ERROR
-
{this.props.name || "Component"} failed to render
+
{this.props.name || "Component"} failed to render
@@ -295,20 +295,20 @@ export default function FilterPanel({ data, activeFilters, setActiveFilters }: F return (
setOpenModal(section.key)} >
{section.icon} - {section.title} + {section.title} {count > 0 && ( {count} )}
- +
); diff --git a/frontend/src/components/FindLocateBar.tsx b/frontend/src/components/FindLocateBar.tsx index cd43152..6e61c84 100644 --- a/frontend/src/components/FindLocateBar.tsx +++ b/frontend/src/components/FindLocateBar.tsx @@ -171,14 +171,14 @@ export default function FindLocateBar({ data, onLocate, onFilter }: FindLocateBa return (
-
- +
+ { setQuery(e.target.value); setIsOpen(true); @@ -186,11 +186,11 @@ export default function FindLocateBar({ data, onLocate, onFilter }: FindLocateBa onFocus={() => setIsOpen(true)} /> {query && ( - )} - +
@@ -199,21 +199,21 @@ export default function FindLocateBar({ data, onLocate, onFilter }: FindLocateBa initial={{ opacity: 0, y: -4 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -4 }} - className="absolute top-full left-0 right-0 mt-1 bg-black/90 backdrop-blur-md border border-gray-800 rounded-lg overflow-hidden z-50 shadow-[0_8px_30px_rgba(0,0,0,0.6)]" + className="absolute top-full left-0 right-0 mt-1 bg-[var(--bg-secondary)]/90 backdrop-blur-md border border-[var(--border-primary)] rounded-lg overflow-hidden z-50 shadow-[0_8px_30px_rgba(0,0,0,0.3)]" >
{filtered.map((r, idx) => ( ))}
-
+
{filtered.length} RESULT{filtered.length !== 1 ? 'S' : ''} — CLICK TO LOCATE
@@ -231,9 +231,9 @@ export default function FindLocateBar({ data, onLocate, onFilter }: FindLocateBa initial={{ opacity: 0, y: -4 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -4 }} - className="absolute top-full left-0 right-0 mt-1 bg-black/90 backdrop-blur-md border border-gray-800 rounded-lg z-50 p-4 text-center" + className="absolute top-full left-0 right-0 mt-1 bg-[var(--bg-secondary)]/90 backdrop-blur-md border border-[var(--border-primary)] rounded-lg z-50 p-4 text-center" > -
NO MATCHING ASSETS
+
NO MATCHING ASSETS
)} diff --git a/frontend/src/components/MapLegend.tsx b/frontend/src/components/MapLegend.tsx index bcffc33..b5494d8 100644 --- a/frontend/src/components/MapLegend.tsx +++ b/frontend/src/components/MapLegend.tsx @@ -217,10 +217,10 @@ const MapLegend = React.memo(function MapLegend({ isOpen, onClose }: { isOpen: b animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.95, y: 20 }} transition={{ type: "spring", damping: 25, stiffness: 300 }} - className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[520px] max-h-[80vh] bg-gray-950/95 backdrop-blur-xl border border-cyan-900/50 rounded-xl z-[9999] flex flex-col shadow-[0_0_60px_rgba(0,0,0,0.8)]" + className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[520px] max-h-[80vh] bg-[var(--bg-secondary)]/95 backdrop-blur-xl border border-cyan-900/50 rounded-xl z-[9999] flex flex-col shadow-[0_0_60px_rgba(0,0,0,0.3)]" > {/* Header */} -
+
@@ -230,13 +230,13 @@ const MapLegend = React.memo(function MapLegend({ isOpen, onClose }: { isOpen: b
-

MAP LEGEND

- ICON REFERENCE KEY +

MAP LEGEND

+ ICON REFERENCE KEY
@@ -247,16 +247,16 @@ const MapLegend = React.memo(function MapLegend({ isOpen, onClose }: { isOpen: b {LEGEND.map((cat) => { const isCollapsed = collapsed.has(cat.name); return ( -
+
{/* Category Header */} {/* Items */} @@ -267,13 +267,13 @@ const MapLegend = React.memo(function MapLegend({ isOpen, onClose }: { isOpen: b animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ duration: 0.15 }} - className="border-t border-gray-800/40" + className="border-t border-[var(--border-primary)]/40" >
{cat.items.map((item, idx) => ( -
+
- {item.label} + {item.label}
))}
@@ -286,8 +286,8 @@ const MapLegend = React.memo(function MapLegend({ isOpen, onClose }: { isOpen: b
{/* Footer */} -
-
+
+
{LEGEND.reduce((sum, c) => sum + c.items.length, 0)} ICON DEFINITIONS ACROSS {LEGEND.length} CATEGORIES
diff --git a/frontend/src/components/MaplibreViewer.tsx b/frontend/src/components/MaplibreViewer.tsx index a4b744f..c4e7cf1 100644 --- a/frontend/src/components/MaplibreViewer.tsx +++ b/frontend/src/components/MaplibreViewer.tsx @@ -9,6 +9,7 @@ import ScaleBar from "@/components/ScaleBar"; import maplibregl from "maplibre-gl"; import { AlertTriangle } from "lucide-react"; import WikiImage from "@/components/WikiImage"; +import { useTheme } from "@/lib/ThemeContext"; const svgPlaneCyan = `data:image/svg+xml;utf8,${encodeURIComponent(``)}`; const svgPlaneYellow = `data:image/svg+xml;utf8,${encodeURIComponent(``)}`; @@ -150,13 +151,29 @@ const darkStyle = { } }, layers: [ - { - id: 'carto-dark-layer', + { id: 'carto-dark-layer', type: 'raster', source: 'carto-dark', minzoom: 0, maxzoom: 22 }, + { id: 'imagery-ceiling', type: 'background', paint: { 'background-opacity': 0 } } + ] +}; + +const lightStyle = { + version: 8, + glyphs: "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf", + sources: { + 'carto-light': { type: 'raster', - source: 'carto-dark', - minzoom: 0, - maxzoom: 22 + tiles: [ + "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png", + "https://b.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png", + "https://c.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png", + "https://d.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png" + ], + tileSize: 256 } + }, + layers: [ + { id: 'carto-light-layer', type: 'raster', source: 'carto-light', minzoom: 0, maxzoom: 22 }, + { id: 'imagery-ceiling', type: 'background', paint: { 'background-opacity': 0 } } ] }; @@ -185,8 +202,10 @@ const MISSION_ICON_MAP: Record = { 'commercial_imaging': 'sat-com', 'space_station': 'sat-station' }; -const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, selectedEntity, onMouseCoords, onRightClick, regionDossier, regionDossierLoading, onViewStateChange, measureMode, onMeasureClick, measurePoints }: any) => { +const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, selectedEntity, onMouseCoords, onRightClick, regionDossier, regionDossierLoading, onViewStateChange, measureMode, onMeasureClick, measurePoints, gibsDate, gibsOpacity }: any) => { const mapRef = useRef(null); + const { theme } = useTheme(); + const mapThemeStyle = useMemo(() => theme === 'light' ? lightStyle : darkStyle, [theme]); const [viewState, setViewState] = useState({ longitude: 0, @@ -369,6 +388,29 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele }; }, [activeLayers.cctv, data?.cctv, inView]); + // KiwiSDR receivers — clustered amber dots + const kiwisdrGeoJSON = useMemo(() => { + if (!activeLayers.kiwisdr || !data?.kiwisdr?.length) return null; + return { + type: 'FeatureCollection' as const, + features: data.kiwisdr.filter((k: any) => k.lat != null && k.lon != null && inView(k.lat, k.lon)).map((k: any, i: number) => ({ + type: 'Feature' as const, + properties: { + id: i, + type: 'kiwisdr', + name: k.name || 'Unknown SDR', + url: k.url || '', + users: k.users || 0, + users_max: k.users_max || 0, + bands: k.bands || '', + antenna: k.antenna || '', + location: k.location || '', + }, + geometry: { type: 'Point' as const, coordinates: [k.lon, k.lat] } + })) + }; + }, [activeLayers.kiwisdr, data?.kiwisdr, inView]); + // Load Images into the Map Style once loaded const onMapLoad = useCallback((e: any) => { const map = e.target; @@ -1102,7 +1144,8 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele frontlineGeoJSON && 'ukraine-frontline-layer', earthquakesGeoJSON && 'earthquakes-layer', satellitesGeoJSON && 'satellites-layer', - cctvGeoJSON && 'cctv-layer' + cctvGeoJSON && 'cctv-layer', + kiwisdrGeoJSON && 'kiwisdr-layer' ].filter(Boolean) as string[]; @@ -1134,7 +1177,7 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele evt.preventDefault(); onRightClick?.({ lat: evt.lngLat.lat, lng: evt.lngLat.lng }); }} - mapStyle={darkStyle as any} + mapStyle={mapThemeStyle as any} mapLib={maplibregl} onLoad={onMapLoad} onIdle={updateBounds} @@ -1162,6 +1205,50 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele } }} > + {/* Esri World Imagery — high-res static satellite (zoom 0-18+) */} + {activeLayers.highres_satellite && ( + + + + )} + + {/* NASA GIBS MODIS Terra — daily satellite imagery overlay */} + {activeLayers.gibs_imagery && gibsDate && ( + + + + )} + {/* SOLAR TERMINATOR — night overlay */} {activeLayers.day_night && nightGeoJSON && ( @@ -1782,6 +1869,43 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele )} + {/* KiwiSDR Receivers — clustered amber dots */} + {kiwisdrGeoJSON && ( + + + + + + )} + {/* Satellite positions — mission-type icons */} {satellitesGeoJSON && ( @@ -1851,7 +1975,7 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele Altitude: {sat.alt_km?.toLocaleString()} km
{sat.wiki && ( -
+
)} @@ -1903,7 +2027,7 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele
)} {uav.wiki && ( -
+
)} @@ -1922,25 +2046,25 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele anchor="bottom" offset={15} > -
+

NEWS ON THE GROUND

- +
-
- LOCATION +
+ LOCATION {data.gdelt[selectedEntity.id as number].properties?.name || 'UNKNOWN REGION'}
- LATEST REPORTS: ({data.gdelt[selectedEntity.id as number].properties?.count || 1}) + LATEST REPORTS: ({data.gdelt[selectedEntity.id as number].properties?.count || 1})
{(() => { const urls: string[] = data.gdelt[selectedEntity.id as number].properties?._urls_list || []; const headlines: string[] = data.gdelt[selectedEntity.id as number].properties?._headlines_list || []; - if (urls.length === 0) return No articles available.; + if (urls.length === 0) return No articles available.; return urls.map((url: string, idx: number) => ( e.stopPropagation()} - className="text-orange-400 text-[9px] underline hover:text-orange-300 block py-1 border-b border-gray-800/50 last:border-0 cursor-pointer" + className="text-orange-400 text-[9px] underline hover:text-orange-300 block py-1 border-b border-[var(--border-primary)]/50 last:border-0 cursor-pointer" style={{ pointerEvents: 'all' }} > {headlines[idx] || url} @@ -1976,19 +2100,19 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele anchor="bottom" offset={15} > -
+

REGIONAL TACTICAL EVENT

- +
-
+
{item.title}
-
- TIME +
+ TIME {item.timestamp || 'UNKNOWN'}
{item.link && ( @@ -2036,22 +2160,22 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele anchor="bottom" offset={25} > -
+

THREAT INTERCEPT

LVL: {item.risk_score}/10 - +
-
+
{item.title}
-
- SOURCE +
+ SOURCE {item.source || 'UNKNOWN'}
{item.machine_assessment && ( @@ -2096,6 +2220,65 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele )} + {/* SENTINEL-2 IMAGERY — floating intel card on map near right-click */} + {selectedEntity?.type === 'region_dossier' && selectedEntity.extra && regionDossier?.sentinel2 && !regionDossierLoading && ( + +
+ {/* Header bar */} +
+
+
+ SENTINEL-2 IMAGERY +
+ {selectedEntity.extra.lat.toFixed(3)}, {selectedEntity.extra.lng.toFixed(3)} +
+ + {regionDossier.sentinel2.found ? ( + <> + {/* Metadata row */} +
+ {regionDossier.sentinel2.platform} + {regionDossier.sentinel2.datetime?.slice(0, 10)} + {regionDossier.sentinel2.cloud_cover?.toFixed(0)}% cloud +
+ + {/* Thumbnail */} + {regionDossier.sentinel2.thumbnail_url ? ( + + Sentinel-2 scene + + ) : ( +
Scene found — no preview available
+ )} + + {/* Footer */} +
+ CLICK IMAGE TO OPEN FULL RESOLUTION +
+ + ) : ( +
+ No clear imagery in last 30 days +
+ )} +
+ + )} + {/* MEASUREMENT LINES */} {measurePoints && measurePoints.length >= 2 && ( {/* Header Toggle */}
setIsMinimized(!isMinimized)} > - GLOBAL MARKETS -
@@ -36,7 +36,7 @@ const MarketsPanel = React.memo(function MarketsPanel({ data }: { data: any }) { exit={{ height: 0, opacity: 0 }} className="overflow-y-auto styled-scrollbar flex flex-col gap-4 p-4 pt-3 max-h-[400px]" > -
+

DEFENSE SEC TICKERS

@@ -45,7 +45,7 @@ const MarketsPanel = React.memo(function MarketsPanel({ data }: { data: any }) {
[{ticker}]
- ${info.price.toFixed(2)} + ${info.price.toFixed(2)} {info.up ? : } {Math.abs(info.change_percent).toFixed(2)}% @@ -65,7 +65,7 @@ const MarketsPanel = React.memo(function MarketsPanel({ data }: { data: any }) {
{name}
- ${info.price.toFixed(2)} + ${info.price.toFixed(2)} {info.up ? : } {Math.abs(info.change_percent).toFixed(2)}% diff --git a/frontend/src/components/NewsFeed.tsx b/frontend/src/components/NewsFeed.tsx index 85f2a81..56fb67f 100644 --- a/frontend/src/components/NewsFeed.tsx +++ b/frontend/src/components/NewsFeed.tsx @@ -199,7 +199,7 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi >

REGION DOSSIER

- + {selectedEntity.extra ? `${selectedEntity.extra.lat.toFixed(3)}, ${selectedEntity.extra.lng.toFixed(3)}` : ''}
@@ -211,41 +211,43 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi
{/* COUNTRY */}
COUNTRY LEVEL {d.country?.flag_emoji || ''}
-
COUNTRY{d.country?.name}
+
COUNTRY{d.country?.name}
{d.country?.official_name && d.country.official_name !== d.country.name && ( -
OFFICIAL{d.country.official_name}
+
OFFICIAL{d.country.official_name}
)} -
LEADER{d.country?.leader}
-
GOVERNMENT{d.country?.government_type}
-
POPULATION{d.country?.population?.toLocaleString()}
-
CAPITAL{d.country?.capital}
-
LANGUAGES{d.country?.languages?.join(', ')}
+
LEADER{d.country?.leader}
+
GOVERNMENT{d.country?.government_type}
+
POPULATION{d.country?.population?.toLocaleString()}
+
CAPITAL{d.country?.capital}
+
LANGUAGES{d.country?.languages?.join(', ')}
{d.country?.currencies?.length > 0 && ( -
CURRENCY{d.country.currencies.join(', ')}
+
CURRENCY{d.country.currencies.join(', ')}
)} -
REGION{d.country?.subregion || d.country?.region}
+
REGION{d.country?.subregion || d.country?.region}
{d.country?.area_km2 > 0 && ( -
AREA{d.country.area_km2.toLocaleString()} km²
+
AREA{d.country.area_km2.toLocaleString()} km²
)} {/* LOCAL */} {(d.local?.name || d.local?.state) && ( <>
LOCAL LEVEL
- {d.local.name &&
LOCALITY{d.local.name}
} - {d.local.state &&
STATE/PROVINCE{d.local.state}
} - {d.local.description &&
TYPE{d.local.description}
} + {d.local.name &&
LOCALITY{d.local.name}
} + {d.local.state &&
STATE/PROVINCE{d.local.state}
} + {d.local.description &&
TYPE{d.local.description}
} {d.local.summary && ( -
+
>_ INTEL: {d.local.summary.length > 500 ? d.local.summary.substring(0, 500) + '...' : d.local.summary}
)} )} + + {/* Sentinel-2 imagery now shown as map popup — see MaplibreViewer */}
) : d?.error ? ( -
{d.error}
+
{d.error}
) : (
INTEL UNAVAILABLE
)} @@ -263,34 +265,34 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi }; const alertBorderMap: Record = { 'pink': 'border-pink-500/30', 'red': 'border-red-500/30', - 'darkblue': 'border-blue-500/30', 'white': 'border-gray-500/30' + 'darkblue': 'border-blue-500/30', 'white': 'border-[var(--border-primary)]/30' }; const alertBgMap: Record = { 'pink': 'bg-pink-950/40', 'red': 'bg-red-950/40', - 'darkblue': 'bg-blue-950/40', 'white': 'bg-gray-900/40' + 'darkblue': 'bg-blue-950/40', 'white': 'bg-[var(--bg-panel)]' }; const ac = flight.alert_color || 'white'; const headerColor = alertColorMap[ac] || 'text-white'; - const borderColor = alertBorderMap[ac] || 'border-gray-500/30'; - const bgColor = alertBgMap[ac] || 'bg-gray-900/40'; + const borderColor = alertBorderMap[ac] || 'border-[var(--border-primary)]/30'; + const bgColor = alertBgMap[ac] || 'bg-[var(--bg-panel)]'; return (

⚠ TRACKED AIRCRAFT — {flight.alert_category || "ALERT"}

- TRK: {callsign} + TRK: {callsign}
-
- OPERATOR +
+ OPERATOR {flight.alert_operator && flight.alert_operator !== "UNKNOWN" ? ( {/* Owner/Operator Wikipedia photo */} {flight.alert_operator && flight.alert_operator !== "UNKNOWN" && ( -
+
+
{AIRCRAFT_WIKI[flight.model] {aircraftWikiUrl && ( @@ -334,65 +336,65 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi )}
)} -
- CATEGORY +
+ CATEGORY {flight.alert_category || "N/A"}
-
- AIRCRAFT - {flight.alert_type || flight.model || "UNKNOWN"} +
+ AIRCRAFT + {flight.alert_type || flight.model || "UNKNOWN"}
-
- REGISTRATION - {flight.registration || "N/A"} +
+ REGISTRATION + {flight.registration || "N/A"}
{flight.alert_tag1 && ( -
- INTEL TAG +
+ INTEL TAG {flight.alert_tag1}
)} {flight.alert_tag2 && ( -
- SECONDARY - {flight.alert_tag2} +
+ SECONDARY + {flight.alert_tag2}
)} {flight.alert_tag3 && ( -
- DETAIL - {flight.alert_tag3} +
+ DETAIL + {flight.alert_tag3}
)} -
- ALTITUDE - {(Math.round((flight.alt || 0) / 0.3048)).toLocaleString()} ft +
+ ALTITUDE + {(Math.round((flight.alt || 0) / 0.3048)).toLocaleString()} ft
-
- GROUND SPEED - {flight.speed_knots ? `${flight.speed_knots} kts` : 'N/A'} +
+ GROUND SPEED + {flight.speed_knots ? `${flight.speed_knots} kts` : 'N/A'}
-
- HEADING - {Math.round(flight.heading || 0)}° +
+ HEADING + {Math.round(flight.heading || 0)}°
{flight.squawk && ( -
- SQUAWK - {flight.squawk}{flight.squawk === '7700' ? ' ⚠ EMERGENCY' : flight.squawk === '7600' ? ' COMMS LOST' : ''} +
+ SQUAWK + {flight.squawk}{flight.squawk === '7700' ? ' ⚠ EMERGENCY' : flight.squawk === '7600' ? ' COMMS LOST' : ''}
)} {flight.alert_link && ( -
- REFERENCE + )} {flight.icao24 && ( -
- FLIGHT RECORD +
+ FLIGHT RECORD View History Log @@ -451,34 +453,34 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi

{selectedEntity.type === 'military_flight' ? "MILITARY BOGEY INTERCEPT" : selectedEntity.type === 'private_flight' ? "PRIVATE TRANSPONDER" : selectedEntity.type === 'private_jet' ? "PRIVATE JET TRANSPONDER" : "COMMERCIAL TRANSPONDER"}

- TRK: {callsign} + TRK: {callsign}
-
- OPERATOR - {airline} +
+ OPERATOR + {airline}
-
- REGISTRATION - {flight.registration || "N/A"} +
+ REGISTRATION + {flight.registration || "N/A"}
-
- AIRCRAFT MODEL - {flight.model || "UNKNOWN"} +
+ AIRCRAFT MODEL + {flight.model || "UNKNOWN"}
{/* Aircraft photo + Wikipedia link */} {(aircraftImgUrl || aircraftImgLoading || aircraftWikiUrl) && ( -
+
{aircraftImgLoading && ( -
+
)} {aircraftImgUrl && ( {AIRCRAFT_WIKI[flight.model] @@ -491,31 +493,31 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi )}
)} -
- ALTITUDE - {(Math.round((flight.alt || 0) / 0.3048)).toLocaleString()} ft +
+ ALTITUDE + {(Math.round((flight.alt || 0) / 0.3048)).toLocaleString()} ft
-
- GROUND SPEED - {flight.speed_knots ? `${flight.speed_knots} kts` : 'N/A'} +
+ GROUND SPEED + {flight.speed_knots ? `${flight.speed_knots} kts` : 'N/A'}
-
- HEADING - {Math.round(flight.heading || 0)}° +
+ HEADING + {Math.round(flight.heading || 0)}°
{flight.squawk && ( -
- SQUAWK - {flight.squawk}{flight.squawk === '7700' ? ' ⚠ EMERGENCY' : flight.squawk === '7600' ? ' COMMS LOST' : ''} +
+ SQUAWK + {flight.squawk}{flight.squawk === '7700' ? ' ⚠ EMERGENCY' : flight.squawk === '7600' ? ' COMMS LOST' : ''}
)} -
- ROUTE +
+ ROUTE {flight.origin_name !== "UNKNOWN" ? `[${flight.origin_name}] → [${flight.dest_name}]` : "UNKNOWN"}
{flight.icao24 && ( -
- FLIGHT RECORD +
+ FLIGHT RECORD View History Log @@ -548,7 +550,7 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi 'military_vessel': 'text-yellow-400', 'carrier': 'text-orange-400', }; - const headerColor = headerColorMap[ship.type] || 'text-gray-400'; + const headerColor = headerColorMap[ship.type] || 'text-[var(--text-secondary)]'; const headerTitleMap: Record = { 'tanker': 'AIS TANKER INTERCEPT', @@ -571,49 +573,49 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi

{headerTitle}

- MMSI: {ship.mmsi || 'N/A'} + MMSI: {ship.mmsi || 'N/A'}
-
- VESSEL NAME - {ship.name || 'UNKNOWN'} +
+ VESSEL NAME + {ship.name || 'UNKNOWN'}
-
- VESSEL TYPE +
+ VESSEL TYPE {typeLabel}
-
- FLAG STATE - {ship.country || 'UNKNOWN'} +
+ FLAG STATE + {ship.country || 'UNKNOWN'}
{ship.callsign && ( -
- CALLSIGN - {ship.callsign} +
+ CALLSIGN + {ship.callsign}
)} {ship.imo > 0 && ( -
- IMO NUMBER - {ship.imo} +
+ IMO NUMBER + {ship.imo}
)} -
- DESTINATION +
+ DESTINATION {ship.destination || 'UNKNOWN'}
-
- SPEED (SOG) - {ship.type === 'carrier' ? 'UNKNOWN' : `${ship.sog || 0} kts`} +
+ SPEED (SOG) + {ship.type === 'carrier' ? 'UNKNOWN' : `${ship.sog || 0} kts`}
-
- COURSE (COG) - {ship.type === 'carrier' ? 'UNKNOWN' : `${Math.round(ship.cog || 0)}°`} +
+ COURSE (COG) + {ship.type === 'carrier' ? 'UNKNOWN' : `${Math.round(ship.cog || 0)}°`}
{ship.mmsi && ( -
- VESSEL RECORD +
+ VESSEL RECORD View on MarineTraffic @@ -621,7 +623,7 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi )} {/* Ship/Carrier Wikipedia photo */} {(ship.wiki || VESSEL_TYPE_WIKI[ship.type]) && ( -
+
MILITARY INCIDENT CLUSTER - ID: {selectedEntity.id} + ID: {selectedEntity.id}
-
- LOCATION - {props.name || 'UNKNOWN REGION'} +
+ LOCATION + {props.name || 'UNKNOWN REGION'}
-
- ARTICLE COUNT +
+ ARTICLE COUNT {props.count || 1}
- LATEST REPORTS: + LATEST REPORTS:
@@ -690,25 +692,25 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi

REGIONAL TACTICAL EVENT

- ID: {item.id} + ID: {item.id}
-
- REGION - {item.region || 'UNKNOWN'} +
+ REGION + {item.region || 'UNKNOWN'}
-
- DESCRIPTION +
+ DESCRIPTION {item.title}
-
- REPORTED TIME - {item.timestamp || 'UNKNOWN'} +
+ REPORTED TIME + {item.timestamp || 'UNKNOWN'}
{item.link && (
- SOURCE + SOURCE View Liveuamap Report @@ -734,16 +736,16 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi

THREAT INTERCEPT

- LVL: {item.risk_score}/10 + LVL: {item.risk_score}/10
-
- SOURCE - {item.source || 'UNKNOWN'} +
+ SOURCE + {item.source || 'UNKNOWN'}
-
- HEADLINE +
+ HEADLINE {item.title}
{item.machine_assessment && ( @@ -755,7 +757,7 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi )} {item.link && (
- REFERENCE + REFERENCE View Source Article @@ -781,20 +783,20 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi

AERONAUTICAL HUB

- IATA: {apt.iata} + IATA: {apt.iata}
-
- FACILITY NAME - {apt.name} +
+ FACILITY NAME + {apt.name}
-
- COORDINATES - {apt.lat.toFixed(4)}, {apt.lng.toFixed(4)} +
+ COORDINATES + {apt.lat.toFixed(4)}, {apt.lng.toFixed(4)}
-
- STATUS +
+ STATUS OPERATIONAL
@@ -817,7 +819,7 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi ? new Date(selectedEntity.extra.last_updated + 'Z').toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, timeZoneName: 'short' }).toUpperCase() + ' — OPTIC INTERCEPT' : 'OPTIC INTERCEPT'} - ID: {selectedEntity.id} + ID: {selectedEntity.id}
{(() => { @@ -901,7 +903,7 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi initial={{ y: 50, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.8, delay: 0.2 }} - className={`w-full bg-black/40 backdrop-blur-md border border-gray-800 rounded-xl flex flex-col z-10 font-mono shadow-[0_4px_30px_rgba(0,0,0,0.5)] pointer-events-auto overflow-hidden transition-all duration-300 ${isMinimized ? 'h-[50px] flex-shrink-0' : 'flex-1 min-h-0'}`} + className={`w-full bg-[var(--bg-panel)] backdrop-blur-md border border-[var(--border-primary)] rounded-xl flex flex-col z-10 font-mono shadow-[0_4px_30px_rgba(0,0,0,0.5)] pointer-events-auto overflow-hidden transition-all duration-300 ${isMinimized ? 'h-[50px] flex-shrink-0' : 'flex-1 min-h-0'}`} >
GLOBAL THREAT INTERCEPT -
@@ -969,14 +971,14 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi transition={{ delay: 0.1 + (idx * 0.05) }} className={`p-2 rounded-sm border-l-[2px] border-r border-t border-b ${bgClass} flex flex-col gap-1 relative group shrink-0`} > -
+
>_ {item.source} [{item.published ? formatTime(item.published) : ''}]
- + {item.title} @@ -994,12 +996,12 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi
{item.cluster_count > 1 && ( - )} {item.coords && ( - + {item.coords[0].toFixed(2)}, {item.coords[1].toFixed(2)} )} @@ -1016,7 +1018,7 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi > {item.articles.slice(1).map((subItem: any, subIdx: number) => (
-
+
>_ {subItem.source} = 9 ? 'text-red-400' : @@ -1025,7 +1027,7 @@ function NewsFeedInner({ data, selectedEntity, regionDossier, regionDossierLoadi 'text-green-400' }>LVL: {subItem.risk_score}/10
- + {subItem.title}
diff --git a/frontend/src/components/OnboardingModal.tsx b/frontend/src/components/OnboardingModal.tsx index 7f1c664..afac6b8 100644 --- a/frontend/src/components/OnboardingModal.tsx +++ b/frontend/src/components/OnboardingModal.tsx @@ -89,24 +89,24 @@ const OnboardingModal = React.memo(function OnboardingModal({ onClose, onOpenSet className="fixed inset-0 z-[10001] flex items-center justify-center pointer-events-none" >
e.stopPropagation()} > {/* Header */} -
+
-

MISSION BRIEFING

- FIRST-TIME SETUP +

MISSION BRIEFING

+ FIRST-TIME SETUP
@@ -122,7 +122,7 @@ const OnboardingModal = React.memo(function OnboardingModal({ onClose, onOpenSet className={`flex-1 py-1.5 text-[9px] font-mono tracking-widest rounded border transition-all ${ step === i ? "border-cyan-500/50 text-cyan-400 bg-cyan-950/20" - : "border-gray-800 text-gray-600 hover:border-gray-700 hover:text-gray-400" + : "border-[var(--border-primary)] text-[var(--text-muted)] hover:border-[var(--border-secondary)] hover:text-[var(--text-secondary)]" }`} > {label.toUpperCase()} @@ -135,10 +135,10 @@ const OnboardingModal = React.memo(function OnboardingModal({ onClose, onOpenSet {step === 0 && (
-
+
S H A D O W B R O K E R
-

+

Real-time OSINT dashboard aggregating 12+ live intelligence sources. Flights, ships, satellites, earthquakes, conflicts, and more — all on one map.

@@ -149,7 +149,7 @@ const OnboardingModal = React.memo(function OnboardingModal({ onClose, onOpenSet

API Keys Required

-

+

Two API keys are needed for full functionality: OpenSky Network (flights) and AIS Stream (ships). Both are free. Without them, some panels will show no data.

@@ -162,7 +162,7 @@ const OnboardingModal = React.memo(function OnboardingModal({ onClose, onOpenSet

8 Sources Work Immediately

-

+

Military aircraft, satellites, earthquakes, global conflicts, weather radar, radio scanners, news, and market data all work out of the box — no keys needed.

@@ -190,7 +190,7 @@ const OnboardingModal = React.memo(function OnboardingModal({ onClose, onOpenSet GET KEY
-

{api.description}

+

{api.description}

    {api.steps.map((s, i) => (
  1. @@ -214,17 +214,17 @@ const OnboardingModal = React.memo(function OnboardingModal({ onClose, onOpenSet {step === 2 && (
    -

    +

    These data sources are completely free and require no API keys. They activate automatically on launch.

    {FREE_SOURCES.map((src) => ( -
    +
    {src.icon} - {src.name} + {src.name}
    -

    {src.desc}

    +

    {src.desc}

    ))}
    @@ -233,13 +233,13 @@ const OnboardingModal = React.memo(function OnboardingModal({ onClose, onOpenSet
    {/* Footer */} -
    +