This commit is contained in:
zipfriis
2026-03-16 13:08:54 +01:00
commit 9ed735c133
12 changed files with 3218 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
Trades,Header,DataDiscriminator,Asset Category,Currency,Symbol,Date/Time,Quantity,T. Price,C. Price,Proceeds,Comm/Fee,Basis,Realized P/L,MTM P/L,Code
Trades,Data,Order,Stocks,DKK,NOVOBc,"2026-02-24, 03:05:36",8,241.7,243.65,-1933.6,-10,1943.6,0,15.6,O
Trades,Data,Order,Stocks,DKK,NOVOBc,"2026-02-25, 07:10:24",3,239.5,238.4,-718.5,-10,728.5,0,-3.3,O
Trades,SubTotal,,Stocks,DKK,NOVOBc,,11,,,-2652.1,-20,2672.1,0,12.3,
Trades,Total,,Stocks,DKK,,,,,,-2652.1,-20,2672.1,0,12.3,
Trades,Data,Order,Stocks,USD,CSIQ,"2026-01-20, 14:33:05",8,20.48,20.51,-163.84,-0.37585725,164.21585725,0,0.24,O
Trades,SubTotal,,Stocks,USD,CSIQ,,8,,,-163.84,-0.37585725,164.21585725,0,0.24,
Trades,Data,Order,Stocks,USD,PYPL,"2026-01-20, 14:31:34",-3,55.175,55.08,165.525,-0.35144225,-187.906957,-22.733399,0.285,C
Trades,SubTotal,,Stocks,USD,PYPL,,-3,,,165.525,-0.35144225,-187.906957,-22.733399,0.285,
Trades,Data,Order,Equity and Index Options,USD,RGTI 15JAN27 5.5 P,"2026-02-24, 09:34:38",1,0.53,0.5271,-53,-1.05125,54.05125,0,-0.29,O
Trades,SubTotal,,Equity and Index Options,USD,RGTI 15JAN27 5.5 P,,1,,,-53,-1.05125,54.05125,0,-0.29,
1 Trades Header DataDiscriminator Asset Category Currency Symbol Date/Time Quantity T. Price C. Price Proceeds Comm/Fee Basis Realized P/L MTM P/L Code
2 Trades Data Order Stocks DKK NOVOBc 2026-02-24, 03:05:36 8 241.7 243.65 -1933.6 -10 1943.6 0 15.6 O
3 Trades Data Order Stocks DKK NOVOBc 2026-02-25, 07:10:24 3 239.5 238.4 -718.5 -10 728.5 0 -3.3 O
4 Trades SubTotal Stocks DKK NOVOBc 11 -2652.1 -20 2672.1 0 12.3
5 Trades Total Stocks DKK -2652.1 -20 2672.1 0 12.3
6 Trades Data Order Stocks USD CSIQ 2026-01-20, 14:33:05 8 20.48 20.51 -163.84 -0.37585725 164.21585725 0 0.24 O
7 Trades SubTotal Stocks USD CSIQ 8 -163.84 -0.37585725 164.21585725 0 0.24
8 Trades Data Order Stocks USD PYPL 2026-01-20, 14:31:34 -3 55.175 55.08 165.525 -0.35144225 -187.906957 -22.733399 0.285 C
9 Trades SubTotal Stocks USD PYPL -3 165.525 -0.35144225 -187.906957 -22.733399 0.285
10 Trades Data Order Equity and Index Options USD RGTI 15JAN27 5.5 P 2026-02-24, 09:34:38 1 0.53 0.5271 -53 -1.05125 54.05125 0 -0.29 O
11 Trades SubTotal Equity and Index Options USD RGTI 15JAN27 5.5 P 1 -53 -1.05125 54.05125 0 -0.29
+218
View File
@@ -0,0 +1,218 @@
Statement,Header,Field Name,Field Value
Statement,Data,BrokerName,Interactive Brokers Ireland Limited
Statement,Data,BrokerAddress,
Statement,Data,Title,Activity Statement
Statement,Data,Period,"January 1, 2026 - February 27, 2026"
Statement,Data,WhenGenerated,"2026-03-02, 05:30:59 EST"
Account Information,Header,Field Name,Field Value
Account Information,Data,Name,sebastian friis
Account Information,Data,Account,U16113532
Account Information,Data,Account Type,Individual
Account Information,Data,Customer Type,Individual
Account Information,Data,Account Capabilities,Cash
Account Information,Data,Base Currency,DKK
Net Asset Value,Header,Asset Class,Prior Total,Current Long,Current Short,Current Total,Change
Net Asset Value,Data,Cash ,370.525206138,94.622953248,0,94.622953248,-275.90225289
Net Asset Value,Data,Stock,32606.96526,33734.356808,0,33734.356808,1127.391548
Net Asset Value,Data,Options,0,316.913772,0,316.913772,316.913772
Net Asset Value,Data,Total,32977.490466138,34145.893533248,0,34145.893533248,1168.40306711
Net Asset Value,Header,Time Weighted Rate of Return
Net Asset Value,Data,-4.866005514%
Change in NAV,Header,Field Name,Field Value
Change in NAV,Data,Starting Value,32977.490466138
Change in NAV,Data,Mark-to-Market,-1533.453864
Change in NAV,Data,Deposits & Withdrawals,3000
Change in NAV,Data,Other Fees,-213.47
Change in NAV,Data,Commissions,-31.30556903
Change in NAV,Data,Sales Tax,-53.3675
Change in NAV,Data,Ending Value,34145.893533248
Mark-to-Market Performance Summary,Header,Asset Category,Symbol,Prior Quantity,Current Quantity,Prior Price,Current Price,Mark-to-Market P/L Position,Mark-to-Market P/L Transaction,Mark-to-Market P/L Commissions,Mark-to-Market P/L Other,Mark-to-Market P/L Total,Code
Mark-to-Market Performance Summary,Data,Stocks,CSIQ,0,8,--,17.7100,-139.693072,1.529136,-2.39473688,0,-140.55867288,
Mark-to-Market Performance Summary,Data,Stocks,DFDSc,35,35,95.6500,103.8000,285.25,0,0,0,285.25,
Mark-to-Market Performance Summary,Data,Stocks,MAERAc,1,1,"14,640.0000","15,500.0000",860,0,0,0,860,
Mark-to-Market Performance Summary,Data,Stocks,NOVOBc,34,45,325.2500,237.9000,-3017.4,12.3,-20,0,-3025.1,
Mark-to-Market Performance Summary,Data,Stocks,ORSTED,20,20,122.3500,150.0000,553,0,0,0,553,
Mark-to-Market Performance Summary,Data,Stocks,PYPL,3,0,58.3800,--,-63.407469,1.815849,-2.23917915,0,-63.83079915,
Mark-to-Market Performance Summary,Data,Total,,,,,,-1522.250541,15.644985,-24.63391603,0,-1531.23947203,
Mark-to-Market Performance Summary,Data,Equity and Index Options,RGTI 15JAN27 5.5 P,0,1,--,0.5012,-16.410319,-1.840456,-6.671653,0,-24.922428,
Mark-to-Market Performance Summary,Data,Total,,,,,,-16.410319,-1.840456,-6.671653,0,-24.922428,
Mark-to-Market Performance Summary,Data,Forex,DKK,370.525206138,94.622953248,1.0000,1.0000,0,-0.035793,0,0,-0.035793,
Mark-to-Market Performance Summary,Data,Forex,USD,0,0,6.3590,6.3231,-8.56174,0,0,0,-8.56174,
Mark-to-Market Performance Summary,Data,Total,,,,,,-8.56174,-0.035793,0,0,-8.597533,
Mark-to-Market Performance Summary,Data,Total (All Assets),,,,,,-1547.2226,13.768736,-31.30556903,0,-1564.75943303,
Mark-to-Market Performance Summary,Data,Other Fees,,,,,,,,,,-213.47,
Total P/L for Statement Period,,,,,,,,,,,,-1778.22943303,
Realized & Unrealized Performance Summary,Header,Asset Category,Symbol,Cost Adj.,Realized S/T Profit,Realized S/T Loss,Realized L/T Profit,Realized L/T Loss,Realized Total,Unrealized S/T Profit,Unrealized S/T Loss,Unrealized L/T Profit,Unrealized L/T Loss,Unrealized Total,Total,Code
Realized & Unrealized Performance Summary,Data,Stocks,CSIQ,0,0,0,0,0,0,0,-142.496477397,0,0,-142.496477397,-142.496477397,
Realized & Unrealized Performance Summary,Data,Stocks,DFDS,0,0,0,0,0,0,560.05361,-70.6,0,0,489.45361,489.45361,
Realized & Unrealized Performance Summary,Data,Stocks,MAERSK A,0,0,0,0,0,0,3085.536223,0,0,0,3085.536223,3085.536223,
Realized & Unrealized Performance Summary,Data,Stocks,NOVO B,0,0,0,0,0,0,0,-2844.898855,0,0,-2844.898855,-2844.898855,
Realized & Unrealized Performance Summary,Data,Stocks,ORSTED,0,0,0,0,0,0,424,0,0,0,424,424,
Realized & Unrealized Performance Summary,Data,Stocks,PYPL,0,0,-144.84357998,0,0,-144.84357998,0,0,0,0,0,-144.84357998,
Realized & Unrealized Performance Summary,Data,Total,,0,0,-144.84357998,0,0,-144.84357998,4069.589833,-3057.995332397,0,0,1011.594500603,866.750920623,
Realized & Unrealized Performance Summary,Data,Equity and Index Options,RGTI 15JAN27 5.5 P,0,0,0,0,0,0,0,-24.857686875,0,0,-24.857686875,-24.857686875,
Realized & Unrealized Performance Summary,Data,Total,,0,0,0,0,0,0,0,-24.857686875,0,0,-24.857686875,-24.857686875,
Realized & Unrealized Performance Summary,Data,Forex,USD,0,0,-0.035793,0,0,-0.035793,0,0,0,0,0,-0.035793,
Realized & Unrealized Performance Summary,Data,Total,,0,0,-0.035793,0,0,-0.035793,0,0,0,0,0,-0.035793,
Realized & Unrealized Performance Summary,Data,Total (All Assets),,0,0,-144.87937298,0,0,-144.87937298,4069.589833,-3082.853019272,0,0,986.736813728,841.857440748,
Cash Report,Header,Currency Summary,Currency,Total,Securities,Futures,
Cash Report,Data,Starting Cash,Base Currency Summary,370.525206138,370.525206138,0,
Cash Report,Data,Commissions,Base Currency Summary,-31.30556903,-31.30556903,0,
Cash Report,Data,Deposits,Base Currency Summary,3000,3000,0,
Cash Report,Data,Trades (Sales),Base Currency Summary,1428.53983211,1428.53983211,0,
Cash Report,Data,Trades (Purchase),Base Currency Summary,-4406.29901597,-4406.29901597,0,
Cash Report,Data,Other Fees,Base Currency Summary,-213.47,-213.47,0,
Cash Report,Data,Sales Tax,DKK,-53.3675,-53.3675,0,
Cash Report,Data,Ending Cash,Base Currency Summary,94.622953248,94.622953248,0,
Cash Report,Data,Ending Settled Cash,Base Currency Summary,94.622953248,94.622953248,0,
Cash Report,Data,Starting Cash,DKK,370.525206138,370.525206138,0,
Cash Report,Data,Commissions,DKK,-20,-20,0,
Cash Report,Data,Deposits,DKK,3000,3000,0,
Cash Report,Data,Trades (Sales),DKK,18.49044711,18.49044711,0,
Cash Report,Data,Trades (Purchase),DKK,-3007.5552,-3007.5552,0,
Cash Report,Data,Other Fees,DKK,-213.47,-213.47,0,
Cash Report,Data,Sales Tax,DKK,-53.3675,-53.3675,0,
Cash Report,Data,Ending Cash,DKK,94.622953248,94.622953248,0,
Cash Report,Data,Ending Settled Cash,DKK,94.622953248,94.622953248,0,
Cash Report,Data,Starting Cash,USD,0,0,0,
Cash Report,Data,Commissions,USD,-1.7785495,-1.7785495,0,
Cash Report,Data,Trades (Sales),USD,221.525,221.525,0,
Cash Report,Data,Trades (Purchase),USD,-219.7464505,-219.7464505,0,
Cash Report,Data,Ending Cash,USD,0,0,0,
Cash Report,Data,Ending Settled Cash,USD,0,0,0,
Open Positions,Header,DataDiscriminator,Asset Category,Currency,Symbol,Quantity,Mult,Cost Price,Cost Basis,Close Price,Value,Unrealized P/L,Code
Open Positions,Data,Summary,Stocks,DKK,DFDSc,35,1,89.815611143,3143.54639,103.8,3633,489.45361,
Open Positions,Data,Summary,Stocks,DKK,MAERAc,1,1,12414.463777,12414.463777,15500,15500,3085.536223,
Open Positions,Data,Summary,Stocks,DKK,NOVOBc,45,1,301.119974556,13550.398855,237.9,10705.5,-2844.898855,
Open Positions,Data,Summary,Stocks,DKK,ORSTED,20,1,128.8,2576,150,3000,424,
Open Positions,Total,,Stocks,DKK,,,,,31684.409022,,32838.5,1154.090978,
Open Positions,Data,Summary,Stocks,USD,CSIQ,8,1,20.526982125,164.215857,17.71,141.68,-22.535857,
Open Positions,Total,,Stocks,USD,,,,,164.215857,,141.68,-22.535857,
Open Positions,Total,,Stocks,DKK,,,,,1038.353285397,,895.856808,-142.496477397,
Open Positions,Total,,Stocks,DKK,,,,,32722.762307397,,33734.356808,1011.594500603,
Open Positions,Header,DataDiscriminator,Asset Category,Currency,Symbol,Quantity,Mult,Cost Price,Cost Basis,Close Price,Value,Unrealized P/L,Code
Open Positions,Data,Summary,Equity and Index Options,USD,RGTI 15JAN27 5.5 P,1,100,0.5405125,54.05125,0.5012,50.12,-3.93125,
Open Positions,Total,,Equity and Index Options,USD,,,,,54.05125,,50.12,-3.93125,
Open Positions,Total,,Equity and Index Options,DKK,,,,,341.771458875,,316.913772,-24.857686875,
Deposits & Withdrawals,Header,Currency,Settle Date,Description,Amount
Deposits & Withdrawals,Data,DKK,2026-02-23,Electronic Fund Transfer,3000
Deposits & Withdrawals,Data,Total,,,3000
Fees,Header,Subtitle,Currency,Date,Description,Amount
Fees,Data,Other Fees,DKK,2026-01-05,s*******42:US Equity and Options Add-On Streaming Bundle Non-Professional for Jan 2026,-28.62
Fees,Data,Other Fees,DKK,2026-01-05,s*******42:Nordic Equity Non-Professional Level I for Jan 2026,-14.94
Fees,Data,Other Fees,DKK,2026-01-05,s*******42:US Securities Snapshot and Futures Value Bundle Non-Professional for Jan 2026,-63.59
Fees,Data,Other Fees,DKK,2026-02-04,s*******42:US Equity and Options Add-On Streaming Bundle Non-Professional for Feb 2026,-28.36
Fees,Data,Other Fees,DKK,2026-02-04,s*******42:Nordic Equity Non-Professional Level I for Feb 2026,-14.94
Fees,Data,Other Fees,DKK,2026-02-04,s*******42:US Securities Snapshot and Futures Value Bundle Non-Professional for Feb 2026,-63.02
Fees,Data,Total,,,,-213.47
Fees,Notes,"Market data is provided by Global Financial Information Services (GmbH). Your local broker collects amounts owed for fees and tax for such data on behalf of Global Financial Information Services (GmbH). Note, you are responsible for any applicable taxes relating to the provision of these services."
Financial Instrument Information,Header,Asset Category,Symbol,Description,Conid,Security ID,Underlying,Listing Exch,Multiplier,Type,Code
Financial Instrument Information,Data,Stocks,CSIQ,CANADIAN SOLAR INC,41498671,CA1366351098,CSIQ,NASDAQ,1,COMMON,
Financial Instrument Information,Data,Stocks,DFDSc,DFDS A/S,206867091,DK0060655629,DFDS,CPH,1,COMMON,
Financial Instrument Information,Data,Stocks,MAERAc,AP MOLLER-MAERSK A/S-A,25846066,DK0010244425,MAERSK.A,CPH,1,COMMON,
Financial Instrument Information,Data,Stocks,NOVOBc,NOVO NORDISK A/S-B,652806383,DK0062498333,NOVO.B,CPH,1,COMMON,
Financial Instrument Information,Data,Stocks,ORSTED,ORSTED A/S,242060078,DK0060094928,ORSTED,CPH,1,COMMON,
Financial Instrument Information,Data,Stocks,PYPL,PAYPAL HOLDINGS INC,199169591,US70450Y1038,PYPL,NASDAQ,1,COMMON,
Financial Instrument Information,Header,Asset Category,Symbol,Description,Conid,Underlying,Listing Exch,Multiplier,Expiry,Delivery Month,Type,Strike,Code
Financial Instrument Information,Data,Equity and Index Options,RGTI 270115P00005500,RGTI 15JAN27 5.5 P,746801714,RGTI,CBOE,100,2027-01-15,2027-01,P,5.5,
Codes,Header,Code,Meaning,Code (Cont.),Meaning (Cont.)
Codes,Data,A,Assignment
Codes,Data,ADR,ADR Fee Accrual
Codes,Data,AEx,Automatic exercise for dividend-related recommendation.
Codes,Data,AFx,AutoFX conversion resulting from trading
Codes,Data,Adj,Adjustment
Codes,Data,Al,Allocation
Codes,Data,Aw,Away Trade
Codes,Data,B,Automatic Buy-in
Codes,Data,Bo,Direct Borrow
Codes,Data,C,Closing Trade
Codes,Data,CD,Cash Delivery
Codes,Data,CP,Complex Position
Codes,Data,Ca,Cancelled
Codes,Data,Co,Corrected Trade
Codes,Data,Cx,Part or all of this transaction was a Crossing executed as dual agent by IB for two IB customers
Codes,Data,DT,Discounted Trade
Codes,Data,De,Delivery or Conversion Action
Codes,Data,ETF,ETF Creation/Redemption
Codes,Data,Ep,Resulted from an Expired Position
Codes,Data,Ex,Exercise
Codes,Data,FP,The fractional portion of this trade was executed against IB or an affiliate.
Codes,Data,FPA,The fractional portion of this trade was executed against IB or an affiliate. IB acted as agent for the whole share portion of this trade.
Codes,Data,G,Trade in Guaranteed Account Segment
Codes,Data,GEA,Exercise or Assignment resulting from offsetting positions
Codes,Data,HC,Highest Cost tax basis election
Codes,Data,HFI,Investment Transferred to Hedge Fund
Codes,Data,HFR,Redemption from Hedge Fund
Codes,Data,I,Internal Transfer
Codes,Data,IA,The transaction was executed against IB or an affiliate
Codes,Data,IM,A portion of the order was executed against IB or an affiliate; IB acted as agent on a portion.
Codes,Data,INV,Investment Transfer from Investor
Codes,Data,IPO,This transaction was executed as part of an IPO in which IB was a member of the selling group and is classified as a Principal trade.
Codes,Data,L,Ordered by IB (Margin Violation)
Codes,Data,LD,Adjusted by Loss Disallowed from Wash Sale
Codes,Data,LF,Liquidation of fractional position by IB
Codes,Data,LI,"Last In, First Out (LIFO) tax basis election"
Codes,Data,LT,Long Term P/L
Codes,Data,Lo,Direct Loan
Codes,Data,M,Entered manually by IB
Codes,Data,MEx,Manual exercise for dividend-related recommendation.
Codes,Data,ML,Maximize Losses tax basis election
Codes,Data,MLG,Maximize Long Term Gain tax basis election
Codes,Data,MLL,Maximize Long Term Loss tax basis election
Codes,Data,MSG,Maximize Short Term Gain tax basis election
Codes,Data,MSL,Maximize Short Term Loss tax basis election
Codes,Data,O,Opening Trade
Codes,Data,Off,Yes and No contracts offset to $1.00 cash settlement
Codes,Data,P,Partial Execution
Codes,Data,PE,Perpetual Investment
Codes,Data,PI,Price Improvement
Codes,Data,PTA,Post Trade Allocation
Codes,Data,Po,Interest or Dividend Accrual Posting
Codes,Data,Pr,Part or all of this transaction was executed by the Exchange as a Crossing by IB against an IB affiliate and is therefore classified as a Principal and not an agency trade
Codes,Data,R,Dividend Reinvestment
Codes,Data,RED,Redemption to Investor
Codes,Data,RI,Recurring Investment
Codes,Data,RP,"IB acted as agent for the fractional share portion of this trade, which was executed by an IB affiliate as riskless principal."
Codes,Data,RPA,IB acted as agent for both the fractional share portion and the whole share portion of this trade; the fractional share portion was executed by an IB Affiliate as riskless principal.
Codes,Data,Rb,Rebill
Codes,Data,Re,Interest or Dividend Accrual Reversal
Codes,Data,Ri,Reimbursement
Codes,Data,S0,Contract settled to zero value
Codes,Data,S1,Contract settled to $1.00
Codes,Data,SF,Trade is subject to IBKR Lite Surcharge Fee if volume of such trades exceeds 10% of the total monthly Lite trade volume
Codes,Data,SI,This order was solicited by Interactive Brokers
Codes,Data,SL,Specific Lot tax basis election
Codes,Data,SO,This order was marked as solicited by your Introducing Broker
Codes,Data,SS,Customer designated this trade for shortened settlement and so is subject to execution at prices above the prevailing market
Codes,Data,ST,Short Term P/L
Codes,Data,T,Transfer
Codes,Data,Un,Unvested shares from stock grant
Codes,Data,XCH,Mutual Fund Exchange Transaction
Notes/Legal Notes,Header,Type,Note
Notes/Legal Notes,Data,Notes,"Stock exchange transactions generally settle on the trade date plus one business day (T+1). Options, futures and US open-end mutual fund transactions also settle on trade date plus one business day. Some exchanges and other transaction types may have longer or shorter settlement periods. Ending settled cash reflects the cash that has actually settled."
Notes/Legal Notes,Data,Notes,Initial and maintenance margin requirements are available within the Account Window of the Trader Workstation.
Notes/Legal Notes,Data,Notes,"Quantities preceded by a ""-"" sign indicate sell transactions. Other transactions are purchases. "
Notes/Legal Notes,Data,Notes,"For shares traded on US markets, Interactive Brokers Ireland Limited (IBIE) acts as agent in executing the fractional portion of your order. In certain circumstances, IBIE routes that portion to an affiliate, which may execute it either as principal or riskless principal. If an IB affiliate executes as i) principal, the order will be executed at the price displayed for the opposite side of the NBBO from your order (the offer if you are buying and the bid if you are selling) at the time of execution, ii) riskless principal, the affiliate will execute that portion of your order at the price it receives for the execution of the whole share. The way in which the fractional portion of the order is executed is indicated by the codes associated with the trade. For shares traded on European Markets, IBIE will generally act as counterparty for any fractional portion of your order for shares, which it will execute as principal, for a total consideration which is equal to (or better than), the total consideration for the execution of a full share available at the European trading venues and SIs to which IBIE has access. In certain circumstances, IBIE may route a full share order to the available European trading venues acting in an Agency capacity and the fractional portion of the order will be allocated to your account at the price traded in the trading venue and the remaining fractional portion will be allocated to an affiliate. "
Notes/Legal Notes,Data,Notes,"In case of partial executions, commissions are charged on the total quantity executed on the original order. The commission is displayed on the first partial execution only."
Notes/Legal Notes,Data,Notes,Trade execution times are displayed in Eastern Time.
Notes/Legal Notes,Data,Notes,Applicable regulatory fees for your transactions are available on the IB website at www.interactivebrokers.com/en/accounts/fees/exchangeAndRegulatoryFees.php.
Notes/Legal Notes,Data,Notes,"Borrow Fee Rate represents the cost to borrow stock expressed in percent per annum. It is applied to the collateral value on the stock borrow contract and is separate from any interest earned on credit cash balances. Similarly, Loan Fee Rate represents the benefit to lend stock. A positive rate indicates a cost to the borrower/benefit to the lender and a negative rate indicates a benefit to the borrower/cost to the lender. In general, the fee rates for hard-to-borrow stocks are higher than for normal availability stocks."
Notes/Legal Notes,Data,Notes,Interest Rate on Customer Collateral represents the interest paid on the collateral posted to the customer's account and received from lending stock. A positive rate indicates a benefit to the lender.
Notes/Legal Notes,Data,Notes,"The closing prices on this Activity Statement are indicative and may come from third-party sources. Interactive Brokers does not warrant the accuracy of the prices provided by third-party sources. Due to time zone differences, certain non-US mutual funds may deliver closing prices after the Activity Statement has been produced and the closing prices for such funds will reflect the previous day's price."
Notes/Legal Notes,Data,Notes,All Market Data and Research services are provided through Global Financial Information Services (GmbH).
Notes/Legal Notes,Data,Notes,"Market data is provided by Global Financial Information Services (GmbH). Your local broker collects amounts owed for fees and tax for such data on behalf of Global Financial Information Services (GmbH). Note, you are responsible for any applicable taxes relating to the provision of these services."
Notes/Legal Notes,Data,Notes,"Directive 2014/65/EU and its implementing measures (MiFID II) applies to all client funds and client financial instruments (Client Assets) on this statement except those Client Assets that are not classified as segregated assets, namely securities lent under the Stock Yield Enhancement Programme as identified in the IBKR Managed Securities Lent section of this statement and Client Assets noted as Not Segregated in the Collateral for Customer Borrowing section of this statement. IBIE applies the MiFID II Client Asset protection requirements to funds and instruments that are not subject to MiFID II in the same way as it applies those requirements to Client Assets. IBIE is also subject to the client asset requirements set out in the Central Bank (Supervision and Enforcement) Act 2013 (Section 48(1))(Investment Firms) Regulations 2023 (=S.I. No. 10 of 2023)."
Notes/Legal Notes,Data,Notes,RUS denotes Ruble balances that have been restricted by Russian authorities.
Notes/Legal Notes,Data,Mutual Fund Notes,"All purchase and sales of mutual funds are calculated against the published NAV (Net Asset Value) of the underlying fund and is displayed on this confirmation as the ""C. Price"" (Closing Price). Due to any fees charged by a fund and/or any share rounding, the net price you pay may vary from the published NAV and is shown on this confirmation as the ""T. Price"" (Trade Price)."
Notes/Legal Notes,Data,Fixed Income Notes,"Call features for bonds or preferred stocks may affect the yield. For zero coupon, compound interest and multiplier securities, there are no periodic payments and securities may be callable below maturity value without notice to holder unless registered. For asset-backed securities, the actual yield may vary depending on the speed at which the underlying note is pre-paid. For additional information regarding bond yield, please contact the IB Help Desk at: ibkr.com/help. If this debt security is unrated by a nationally recognized statistical rating organization, it may pose a high risk of default. You should consult an independent advisor to determine whether unrated bonds are appropriate for your portfolio in light of your goals and your financial circumstances. Fees charged by bond trading centers are included in the cost of bond transactions."
Notes/Legal Notes,Data,Legal Notes,"Please promptly report any inaccuracy or discrepancy in this statement, or in your account, to Interactive Brokers Ireland Limited (""IBIE""). You can contact us by telephone or electronically through the IBIE website at, www.interactivebrokers.ie. If you report an error by telephone, you should re-confirm such oral communication in writing in order to protect your rights, including your rights under the Investor Compensation Scheme."
Notes/Legal Notes,Data,Legal Notes,"Unless otherwise noted, IBIE acted as agent in the execution of the above transactions. IBIE may execute your trades through affiliates or third-party brokers who are members of the relevant exchange or market center. Please see the IBIE CAKID Bank Account List at www.ibkr.ie/cakid for custody details."
Notes/Legal Notes,Data,Legal Notes,"IBIE acts as agent or riskless principal in foreign currency exchange transactions and CFDs. Such transactions are executed against an IBIE affiliate, which acts as principal in such transactions and may have a long or short position and may have profited or lost in connection with the transaction."
Notes/Legal Notes,Data,Legal Notes,"IBIE routes orders in US products through its US affiliate, IBLLC. IBLLC accepts liquidity rebates or other order flow payments from Alternative Trading Systems, market makers and exchanges for certain orders in US stocks. IBLLC receives payment for some US option orders pursuant to exchange-mandated marketing fee programs or other arrangements."
Notes/Legal Notes,Data,Legal Notes,Please promptly notify IB-IE of any material change in your investment objectives or financial situation or circumstances.
Notes/Legal Notes,Data,Legal Notes,"An up-to-date financial statement of IBIE is available for your personal inspection at www.interactivebrokers.ie or at its offices, or a copy of the financial statement will be mailed to you upon your written request."
Notes/Legal Notes,Data,Legal Notes,"IBIE is a member of the Investor Compensation Scheme, which provides protection for customers in the event of the failure of IBIE. Further information on the Investor Compensation Scheme can be found at www.investorcompensation.ie ."
Notes/Legal Notes,Data,Legal Notes,IBIE is incorporated under the laws of Ireland and registered with the Irish Companies Registration Office with company registration number 657406.
Notes/Legal Notes,Data,Legal Notes,IBIE is regulated by the Central Bank of Ireland.
Notes/Legal Notes,Data,Legal Notes,"IBIE's Registered Office address is 91 North Wall Quay, Dublin, D01 H7V7, Ireland."
Notes/Legal Notes,Data,Legal Notes,"IBIE is included in the Central Bank of Ireland's register of authorised firms under number C423427. The CBI's address is New Wapping Street, North Wall Quay, Dublin 1, D01 F7X3. The CBI's website is www.centralbank.ie."
1 Statement Header Field Name Field Value
2 Statement Data BrokerName Interactive Brokers Ireland Limited
3 Statement Data BrokerAddress
4 Statement Data Title Activity Statement
5 Statement Data Period January 1, 2026 - February 27, 2026
6 Statement Data WhenGenerated 2026-03-02, 05:30:59 EST
7 Account Information Header Field Name Field Value
8 Account Information Data Name sebastian friis
9 Account Information Data Account U16113532
10 Account Information Data Account Type Individual
11 Account Information Data Customer Type Individual
12 Account Information Data Account Capabilities Cash
13 Account Information Data Base Currency DKK
14 Net Asset Value Header Asset Class Prior Total Current Long Current Short Current Total Change
15 Net Asset Value Data Cash 370.525206138 94.622953248 0 94.622953248 -275.90225289
16 Net Asset Value Data Stock 32606.96526 33734.356808 0 33734.356808 1127.391548
17 Net Asset Value Data Options 0 316.913772 0 316.913772 316.913772
18 Net Asset Value Data Total 32977.490466138 34145.893533248 0 34145.893533248 1168.40306711
19 Net Asset Value Header Time Weighted Rate of Return
20 Net Asset Value Data -4.866005514%
21 Change in NAV Header Field Name Field Value
22 Change in NAV Data Starting Value 32977.490466138
23 Change in NAV Data Mark-to-Market -1533.453864
24 Change in NAV Data Deposits & Withdrawals 3000
25 Change in NAV Data Other Fees -213.47
26 Change in NAV Data Commissions -31.30556903
27 Change in NAV Data Sales Tax -53.3675
28 Change in NAV Data Ending Value 34145.893533248
29 Mark-to-Market Performance Summary Header Asset Category Symbol Prior Quantity Current Quantity Prior Price Current Price Mark-to-Market P/L Position Mark-to-Market P/L Transaction Mark-to-Market P/L Commissions Mark-to-Market P/L Other Mark-to-Market P/L Total Code
30 Mark-to-Market Performance Summary Data Stocks CSIQ 0 8 -- 17.7100 -139.693072 1.529136 -2.39473688 0 -140.55867288
31 Mark-to-Market Performance Summary Data Stocks DFDSc 35 35 95.6500 103.8000 285.25 0 0 0 285.25
32 Mark-to-Market Performance Summary Data Stocks MAERAc 1 1 14,640.0000 15,500.0000 860 0 0 0 860
33 Mark-to-Market Performance Summary Data Stocks NOVOBc 34 45 325.2500 237.9000 -3017.4 12.3 -20 0 -3025.1
34 Mark-to-Market Performance Summary Data Stocks ORSTED 20 20 122.3500 150.0000 553 0 0 0 553
35 Mark-to-Market Performance Summary Data Stocks PYPL 3 0 58.3800 -- -63.407469 1.815849 -2.23917915 0 -63.83079915
36 Mark-to-Market Performance Summary Data Total -1522.250541 15.644985 -24.63391603 0 -1531.23947203
37 Mark-to-Market Performance Summary Data Equity and Index Options RGTI 15JAN27 5.5 P 0 1 -- 0.5012 -16.410319 -1.840456 -6.671653 0 -24.922428
38 Mark-to-Market Performance Summary Data Total -16.410319 -1.840456 -6.671653 0 -24.922428
39 Mark-to-Market Performance Summary Data Forex DKK 370.525206138 94.622953248 1.0000 1.0000 0 -0.035793 0 0 -0.035793
40 Mark-to-Market Performance Summary Data Forex USD 0 0 6.3590 6.3231 -8.56174 0 0 0 -8.56174
41 Mark-to-Market Performance Summary Data Total -8.56174 -0.035793 0 0 -8.597533
42 Mark-to-Market Performance Summary Data Total (All Assets) -1547.2226 13.768736 -31.30556903 0 -1564.75943303
43 Mark-to-Market Performance Summary Data Other Fees -213.47
44 Total P/L for Statement Period -1778.22943303
45 Realized & Unrealized Performance Summary Header Asset Category Symbol Cost Adj. Realized S/T Profit Realized S/T Loss Realized L/T Profit Realized L/T Loss Realized Total Unrealized S/T Profit Unrealized S/T Loss Unrealized L/T Profit Unrealized L/T Loss Unrealized Total Total Code
46 Realized & Unrealized Performance Summary Data Stocks CSIQ 0 0 0 0 0 0 0 -142.496477397 0 0 -142.496477397 -142.496477397
47 Realized & Unrealized Performance Summary Data Stocks DFDS 0 0 0 0 0 0 560.05361 -70.6 0 0 489.45361 489.45361
48 Realized & Unrealized Performance Summary Data Stocks MAERSK A 0 0 0 0 0 0 3085.536223 0 0 0 3085.536223 3085.536223
49 Realized & Unrealized Performance Summary Data Stocks NOVO B 0 0 0 0 0 0 0 -2844.898855 0 0 -2844.898855 -2844.898855
50 Realized & Unrealized Performance Summary Data Stocks ORSTED 0 0 0 0 0 0 424 0 0 0 424 424
51 Realized & Unrealized Performance Summary Data Stocks PYPL 0 0 -144.84357998 0 0 -144.84357998 0 0 0 0 0 -144.84357998
52 Realized & Unrealized Performance Summary Data Total 0 0 -144.84357998 0 0 -144.84357998 4069.589833 -3057.995332397 0 0 1011.594500603 866.750920623
53 Realized & Unrealized Performance Summary Data Equity and Index Options RGTI 15JAN27 5.5 P 0 0 0 0 0 0 0 -24.857686875 0 0 -24.857686875 -24.857686875
54 Realized & Unrealized Performance Summary Data Total 0 0 0 0 0 0 0 -24.857686875 0 0 -24.857686875 -24.857686875
55 Realized & Unrealized Performance Summary Data Forex USD 0 0 -0.035793 0 0 -0.035793 0 0 0 0 0 -0.035793
56 Realized & Unrealized Performance Summary Data Total 0 0 -0.035793 0 0 -0.035793 0 0 0 0 0 -0.035793
57 Realized & Unrealized Performance Summary Data Total (All Assets) 0 0 -144.87937298 0 0 -144.87937298 4069.589833 -3082.853019272 0 0 986.736813728 841.857440748
58 Cash Report Header Currency Summary Currency Total Securities Futures
59 Cash Report Data Starting Cash Base Currency Summary 370.525206138 370.525206138 0
60 Cash Report Data Commissions Base Currency Summary -31.30556903 -31.30556903 0
61 Cash Report Data Deposits Base Currency Summary 3000 3000 0
62 Cash Report Data Trades (Sales) Base Currency Summary 1428.53983211 1428.53983211 0
63 Cash Report Data Trades (Purchase) Base Currency Summary -4406.29901597 -4406.29901597 0
64 Cash Report Data Other Fees Base Currency Summary -213.47 -213.47 0
65 Cash Report Data Sales Tax DKK -53.3675 -53.3675 0
66 Cash Report Data Ending Cash Base Currency Summary 94.622953248 94.622953248 0
67 Cash Report Data Ending Settled Cash Base Currency Summary 94.622953248 94.622953248 0
68 Cash Report Data Starting Cash DKK 370.525206138 370.525206138 0
69 Cash Report Data Commissions DKK -20 -20 0
70 Cash Report Data Deposits DKK 3000 3000 0
71 Cash Report Data Trades (Sales) DKK 18.49044711 18.49044711 0
72 Cash Report Data Trades (Purchase) DKK -3007.5552 -3007.5552 0
73 Cash Report Data Other Fees DKK -213.47 -213.47 0
74 Cash Report Data Sales Tax DKK -53.3675 -53.3675 0
75 Cash Report Data Ending Cash DKK 94.622953248 94.622953248 0
76 Cash Report Data Ending Settled Cash DKK 94.622953248 94.622953248 0
77 Cash Report Data Starting Cash USD 0 0 0
78 Cash Report Data Commissions USD -1.7785495 -1.7785495 0
79 Cash Report Data Trades (Sales) USD 221.525 221.525 0
80 Cash Report Data Trades (Purchase) USD -219.7464505 -219.7464505 0
81 Cash Report Data Ending Cash USD 0 0 0
82 Cash Report Data Ending Settled Cash USD 0 0 0
83 Open Positions Header DataDiscriminator Asset Category Currency Symbol Quantity Mult Cost Price Cost Basis Close Price Value Unrealized P/L Code
84 Open Positions Data Summary Stocks DKK DFDSc 35 1 89.815611143 3143.54639 103.8 3633 489.45361
85 Open Positions Data Summary Stocks DKK MAERAc 1 1 12414.463777 12414.463777 15500 15500 3085.536223
86 Open Positions Data Summary Stocks DKK NOVOBc 45 1 301.119974556 13550.398855 237.9 10705.5 -2844.898855
87 Open Positions Data Summary Stocks DKK ORSTED 20 1 128.8 2576 150 3000 424
88 Open Positions Total Stocks DKK 31684.409022 32838.5 1154.090978
89 Open Positions Data Summary Stocks USD CSIQ 8 1 20.526982125 164.215857 17.71 141.68 -22.535857
90 Open Positions Total Stocks USD 164.215857 141.68 -22.535857
91 Open Positions Total Stocks DKK 1038.353285397 895.856808 -142.496477397
92 Open Positions Total Stocks DKK 32722.762307397 33734.356808 1011.594500603
93 Open Positions Header DataDiscriminator Asset Category Currency Symbol Quantity Mult Cost Price Cost Basis Close Price Value Unrealized P/L Code
94 Open Positions Data Summary Equity and Index Options USD RGTI 15JAN27 5.5 P 1 100 0.5405125 54.05125 0.5012 50.12 -3.93125
95 Open Positions Total Equity and Index Options USD 54.05125 50.12 -3.93125
96 Open Positions Total Equity and Index Options DKK 341.771458875 316.913772 -24.857686875
97 Deposits & Withdrawals Header Currency Settle Date Description Amount
98 Deposits & Withdrawals Data DKK 2026-02-23 Electronic Fund Transfer 3000
99 Deposits & Withdrawals Data Total 3000
100 Fees Header Subtitle Currency Date Description Amount
101 Fees Data Other Fees DKK 2026-01-05 s*******42:US Equity and Options Add-On Streaming Bundle Non-Professional for Jan 2026 -28.62
102 Fees Data Other Fees DKK 2026-01-05 s*******42:Nordic Equity Non-Professional Level I for Jan 2026 -14.94
103 Fees Data Other Fees DKK 2026-01-05 s*******42:US Securities Snapshot and Futures Value Bundle Non-Professional for Jan 2026 -63.59
104 Fees Data Other Fees DKK 2026-02-04 s*******42:US Equity and Options Add-On Streaming Bundle Non-Professional for Feb 2026 -28.36
105 Fees Data Other Fees DKK 2026-02-04 s*******42:Nordic Equity Non-Professional Level I for Feb 2026 -14.94
106 Fees Data Other Fees DKK 2026-02-04 s*******42:US Securities Snapshot and Futures Value Bundle Non-Professional for Feb 2026 -63.02
107 Fees Data Total -213.47
108 Fees Notes Market data is provided by Global Financial Information Services (GmbH). Your local broker collects amounts owed for fees and tax for such data on behalf of Global Financial Information Services (GmbH). Note, you are responsible for any applicable taxes relating to the provision of these services.
109 Financial Instrument Information Header Asset Category Symbol Description Conid Security ID Underlying Listing Exch Multiplier Type Code
110 Financial Instrument Information Data Stocks CSIQ CANADIAN SOLAR INC 41498671 CA1366351098 CSIQ NASDAQ 1 COMMON
111 Financial Instrument Information Data Stocks DFDSc DFDS A/S 206867091 DK0060655629 DFDS CPH 1 COMMON
112 Financial Instrument Information Data Stocks MAERAc AP MOLLER-MAERSK A/S-A 25846066 DK0010244425 MAERSK.A CPH 1 COMMON
113 Financial Instrument Information Data Stocks NOVOBc NOVO NORDISK A/S-B 652806383 DK0062498333 NOVO.B CPH 1 COMMON
114 Financial Instrument Information Data Stocks ORSTED ORSTED A/S 242060078 DK0060094928 ORSTED CPH 1 COMMON
115 Financial Instrument Information Data Stocks PYPL PAYPAL HOLDINGS INC 199169591 US70450Y1038 PYPL NASDAQ 1 COMMON
116 Financial Instrument Information Header Asset Category Symbol Description Conid Underlying Listing Exch Multiplier Expiry Delivery Month Type Strike Code
117 Financial Instrument Information Data Equity and Index Options RGTI 270115P00005500 RGTI 15JAN27 5.5 P 746801714 RGTI CBOE 100 2027-01-15 2027-01 P 5.5
118 Codes Header Code Meaning Code (Cont.) Meaning (Cont.)
119 Codes Data A Assignment
120 Codes Data ADR ADR Fee Accrual
121 Codes Data AEx Automatic exercise for dividend-related recommendation.
122 Codes Data AFx AutoFX conversion resulting from trading
123 Codes Data Adj Adjustment
124 Codes Data Al Allocation
125 Codes Data Aw Away Trade
126 Codes Data B Automatic Buy-in
127 Codes Data Bo Direct Borrow
128 Codes Data C Closing Trade
129 Codes Data CD Cash Delivery
130 Codes Data CP Complex Position
131 Codes Data Ca Cancelled
132 Codes Data Co Corrected Trade
133 Codes Data Cx Part or all of this transaction was a Crossing executed as dual agent by IB for two IB customers
134 Codes Data DT Discounted Trade
135 Codes Data De Delivery or Conversion Action
136 Codes Data ETF ETF Creation/Redemption
137 Codes Data Ep Resulted from an Expired Position
138 Codes Data Ex Exercise
139 Codes Data FP The fractional portion of this trade was executed against IB or an affiliate.
140 Codes Data FPA The fractional portion of this trade was executed against IB or an affiliate. IB acted as agent for the whole share portion of this trade.
141 Codes Data G Trade in Guaranteed Account Segment
142 Codes Data GEA Exercise or Assignment resulting from offsetting positions
143 Codes Data HC Highest Cost tax basis election
144 Codes Data HFI Investment Transferred to Hedge Fund
145 Codes Data HFR Redemption from Hedge Fund
146 Codes Data I Internal Transfer
147 Codes Data IA The transaction was executed against IB or an affiliate
148 Codes Data IM A portion of the order was executed against IB or an affiliate; IB acted as agent on a portion.
149 Codes Data INV Investment Transfer from Investor
150 Codes Data IPO This transaction was executed as part of an IPO in which IB was a member of the selling group and is classified as a Principal trade.
151 Codes Data L Ordered by IB (Margin Violation)
152 Codes Data LD Adjusted by Loss Disallowed from Wash Sale
153 Codes Data LF Liquidation of fractional position by IB
154 Codes Data LI Last In, First Out (LIFO) tax basis election
155 Codes Data LT Long Term P/L
156 Codes Data Lo Direct Loan
157 Codes Data M Entered manually by IB
158 Codes Data MEx Manual exercise for dividend-related recommendation.
159 Codes Data ML Maximize Losses tax basis election
160 Codes Data MLG Maximize Long Term Gain tax basis election
161 Codes Data MLL Maximize Long Term Loss tax basis election
162 Codes Data MSG Maximize Short Term Gain tax basis election
163 Codes Data MSL Maximize Short Term Loss tax basis election
164 Codes Data O Opening Trade
165 Codes Data Off Yes and No contracts offset to $1.00 cash settlement
166 Codes Data P Partial Execution
167 Codes Data PE Perpetual Investment
168 Codes Data PI Price Improvement
169 Codes Data PTA Post Trade Allocation
170 Codes Data Po Interest or Dividend Accrual Posting
171 Codes Data Pr Part or all of this transaction was executed by the Exchange as a Crossing by IB against an IB affiliate and is therefore classified as a Principal and not an agency trade
172 Codes Data R Dividend Reinvestment
173 Codes Data RED Redemption to Investor
174 Codes Data RI Recurring Investment
175 Codes Data RP IB acted as agent for the fractional share portion of this trade, which was executed by an IB affiliate as riskless principal.
176 Codes Data RPA IB acted as agent for both the fractional share portion and the whole share portion of this trade; the fractional share portion was executed by an IB Affiliate as riskless principal.
177 Codes Data Rb Rebill
178 Codes Data Re Interest or Dividend Accrual Reversal
179 Codes Data Ri Reimbursement
180 Codes Data S0 Contract settled to zero value
181 Codes Data S1 Contract settled to $1.00
182 Codes Data SF Trade is subject to IBKR Lite Surcharge Fee if volume of such trades exceeds 10% of the total monthly Lite trade volume
183 Codes Data SI This order was solicited by Interactive Brokers
184 Codes Data SL Specific Lot tax basis election
185 Codes Data SO This order was marked as solicited by your Introducing Broker
186 Codes Data SS Customer designated this trade for shortened settlement and so is subject to execution at prices above the prevailing market
187 Codes Data ST Short Term P/L
188 Codes Data T Transfer
189 Codes Data Un Unvested shares from stock grant
190 Codes Data XCH Mutual Fund Exchange Transaction
191 Notes/Legal Notes Header Type Note
192 Notes/Legal Notes Data Notes Stock exchange transactions generally settle on the trade date plus one business day (T+1). Options, futures and US open-end mutual fund transactions also settle on trade date plus one business day. Some exchanges and other transaction types may have longer or shorter settlement periods. Ending settled cash reflects the cash that has actually settled.
193 Notes/Legal Notes Data Notes Initial and maintenance margin requirements are available within the Account Window of the Trader Workstation.
194 Notes/Legal Notes Data Notes Quantities preceded by a "-" sign indicate sell transactions. Other transactions are purchases.
195 Notes/Legal Notes Data Notes For shares traded on US markets, Interactive Brokers Ireland Limited (IBIE) acts as agent in executing the fractional portion of your order. In certain circumstances, IBIE routes that portion to an affiliate, which may execute it either as principal or riskless principal. If an IB affiliate executes as i) principal, the order will be executed at the price displayed for the opposite side of the NBBO from your order (the offer if you are buying and the bid if you are selling) at the time of execution, ii) riskless principal, the affiliate will execute that portion of your order at the price it receives for the execution of the whole share. The way in which the fractional portion of the order is executed is indicated by the codes associated with the trade. For shares traded on European Markets, IBIE will generally act as counterparty for any fractional portion of your order for shares, which it will execute as principal, for a total consideration which is equal to (or better than), the total consideration for the execution of a full share available at the European trading venues and SIs to which IBIE has access. In certain circumstances, IBIE may route a full share order to the available European trading venues acting in an Agency capacity and the fractional portion of the order will be allocated to your account at the price traded in the trading venue and the remaining fractional portion will be allocated to an affiliate.
196 Notes/Legal Notes Data Notes In case of partial executions, commissions are charged on the total quantity executed on the original order. The commission is displayed on the first partial execution only.
197 Notes/Legal Notes Data Notes Trade execution times are displayed in Eastern Time.
198 Notes/Legal Notes Data Notes Applicable regulatory fees for your transactions are available on the IB website at www.interactivebrokers.com/en/accounts/fees/exchangeAndRegulatoryFees.php.
199 Notes/Legal Notes Data Notes Borrow Fee Rate represents the cost to borrow stock expressed in percent per annum. It is applied to the collateral value on the stock borrow contract and is separate from any interest earned on credit cash balances. Similarly, Loan Fee Rate represents the benefit to lend stock. A positive rate indicates a cost to the borrower/benefit to the lender and a negative rate indicates a benefit to the borrower/cost to the lender. In general, the fee rates for hard-to-borrow stocks are higher than for normal availability stocks.
200 Notes/Legal Notes Data Notes Interest Rate on Customer Collateral represents the interest paid on the collateral posted to the customer's account and received from lending stock. A positive rate indicates a benefit to the lender.
201 Notes/Legal Notes Data Notes The closing prices on this Activity Statement are indicative and may come from third-party sources. Interactive Brokers does not warrant the accuracy of the prices provided by third-party sources. Due to time zone differences, certain non-US mutual funds may deliver closing prices after the Activity Statement has been produced and the closing prices for such funds will reflect the previous day's price.
202 Notes/Legal Notes Data Notes All Market Data and Research services are provided through Global Financial Information Services (GmbH).
203 Notes/Legal Notes Data Notes Market data is provided by Global Financial Information Services (GmbH). Your local broker collects amounts owed for fees and tax for such data on behalf of Global Financial Information Services (GmbH). Note, you are responsible for any applicable taxes relating to the provision of these services.
204 Notes/Legal Notes Data Notes Directive 2014/65/EU and its implementing measures (MiFID II) applies to all client funds and client financial instruments (Client Assets) on this statement except those Client Assets that are not classified as segregated assets, namely securities lent under the Stock Yield Enhancement Programme as identified in the IBKR Managed Securities Lent section of this statement and Client Assets noted as Not Segregated in the Collateral for Customer Borrowing section of this statement. IBIE applies the MiFID II Client Asset protection requirements to funds and instruments that are not subject to MiFID II in the same way as it applies those requirements to Client Assets. IBIE is also subject to the client asset requirements set out in the Central Bank (Supervision and Enforcement) Act 2013 (Section 48(1))(Investment Firms) Regulations 2023 (=S.I. No. 10 of 2023).
205 Notes/Legal Notes Data Notes RUS denotes Ruble balances that have been restricted by Russian authorities.
206 Notes/Legal Notes Data Mutual Fund Notes All purchase and sales of mutual funds are calculated against the published NAV (Net Asset Value) of the underlying fund and is displayed on this confirmation as the "C. Price" (Closing Price). Due to any fees charged by a fund and/or any share rounding, the net price you pay may vary from the published NAV and is shown on this confirmation as the "T. Price" (Trade Price).
207 Notes/Legal Notes Data Fixed Income Notes Call features for bonds or preferred stocks may affect the yield. For zero coupon, compound interest and multiplier securities, there are no periodic payments and securities may be callable below maturity value without notice to holder unless registered. For asset-backed securities, the actual yield may vary depending on the speed at which the underlying note is pre-paid. For additional information regarding bond yield, please contact the IB Help Desk at: ibkr.com/help. If this debt security is unrated by a nationally recognized statistical rating organization, it may pose a high risk of default. You should consult an independent advisor to determine whether unrated bonds are appropriate for your portfolio in light of your goals and your financial circumstances. Fees charged by bond trading centers are included in the cost of bond transactions.
208 Notes/Legal Notes Data Legal Notes Please promptly report any inaccuracy or discrepancy in this statement, or in your account, to Interactive Brokers Ireland Limited ("IBIE"). You can contact us by telephone or electronically through the IBIE website at, www.interactivebrokers.ie. If you report an error by telephone, you should re-confirm such oral communication in writing in order to protect your rights, including your rights under the Investor Compensation Scheme.
209 Notes/Legal Notes Data Legal Notes Unless otherwise noted, IBIE acted as agent in the execution of the above transactions. IBIE may execute your trades through affiliates or third-party brokers who are members of the relevant exchange or market center. Please see the IBIE CAKID Bank Account List at www.ibkr.ie/cakid for custody details.
210 Notes/Legal Notes Data Legal Notes IBIE acts as agent or riskless principal in foreign currency exchange transactions and CFDs. Such transactions are executed against an IBIE affiliate, which acts as principal in such transactions and may have a long or short position and may have profited or lost in connection with the transaction.
211 Notes/Legal Notes Data Legal Notes IBIE routes orders in US products through its US affiliate, IBLLC. IBLLC accepts liquidity rebates or other order flow payments from Alternative Trading Systems, market makers and exchanges for certain orders in US stocks. IBLLC receives payment for some US option orders pursuant to exchange-mandated marketing fee programs or other arrangements.
212 Notes/Legal Notes Data Legal Notes Please promptly notify IB-IE of any material change in your investment objectives or financial situation or circumstances.
213 Notes/Legal Notes Data Legal Notes An up-to-date financial statement of IBIE is available for your personal inspection at www.interactivebrokers.ie or at its offices, or a copy of the financial statement will be mailed to you upon your written request.
214 Notes/Legal Notes Data Legal Notes IBIE is a member of the Investor Compensation Scheme, which provides protection for customers in the event of the failure of IBIE. Further information on the Investor Compensation Scheme can be found at www.investorcompensation.ie .
215 Notes/Legal Notes Data Legal Notes IBIE is incorporated under the laws of Ireland and registered with the Irish Companies Registration Office with company registration number 657406.
216 Notes/Legal Notes Data Legal Notes IBIE is regulated by the Central Bank of Ireland.
217 Notes/Legal Notes Data Legal Notes IBIE's Registered Office address is 91 North Wall Quay, Dublin, D01 H7V7, Ireland.
218 Notes/Legal Notes Data Legal Notes IBIE is included in the Central Bank of Ireland's register of authorised firms under number C423427. The CBI's address is New Wapping Street, North Wall Quay, Dublin 1, D01 F7X3. The CBI's website is www.centralbank.ie.
+3
View File
@@ -0,0 +1,3 @@
module site
go 1.23.4
+361
View File
@@ -0,0 +1,361 @@
package main
import (
"encoding/csv"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"sync"
"time"
)
// ---------- HTTP Handlers ----------
func HelloHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/hello" {
http.Error(w, "404 not found", http.StatusNotFound)
return
}
if r.Method != "GET" {
http.Error(w, "method is not supported", http.StatusNotFound)
return
}
fmt.Fprintf(w, "Hello You")
}
func Home(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./static/index.html") }
func Portfolio(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./static/portfolio.html")
}
func Infra(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./static/infra.html") }
func Cyber(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./static/cyber.html") }
func About(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./static/about.html") }
func GitPage(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./static/gitpage.html") }
func GitPageTicTacToe(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./static/gitpages/TicTacToe.html")
}
func Styles(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./static/styles.css") }
// TradesHandler handles POST /trades
// It accepts a CSV file upload (field name: "file") and returns enriched trades as JSON.
//
// Example curl:
//
// curl -X POST http://localhost:8081/trades -F "file=@trades.csv"
func TradesHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "only POST is supported", http.StatusMethodNotAllowed)
return
}
// Parse multipart form (max 10MB)
if err := r.ParseMultipartForm(10 << 20); err != nil {
http.Error(w, "failed to parse form: "+err.Error(), http.StatusBadRequest)
return
}
file, _, err := r.FormFile("Trades.csc")
if err != nil {
http.Error(w, `missing "file" field in form-data`, http.StatusBadRequest)
return
}
defer file.Close()
// Parse CSV
reader := csv.NewReader(file)
reader.LazyQuotes = true
reader.TrimLeadingSpace = true
records, err := reader.ReadAll()
if err != nil {
http.Error(w, "invalid CSV: "+err.Error(), http.StatusBadRequest)
return
}
// Use most recent weekday as price date
priceDate := lastWeekday(time.Now().UTC().AddDate(0, 0, -1))
// Enrich and return as JSON
enriched, err := enrichRecords(records, priceDate)
if err != nil {
http.Error(w, "enrichment failed: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(enriched)
}
// ---------- Main ----------
func main() {
http.HandleFunc("/styles.css", Styles)
http.HandleFunc("/", Home)
http.HandleFunc("/about", About)
http.HandleFunc("/portfolio", Portfolio)
http.HandleFunc("/infra", Infra)
http.HandleFunc("/cyber", Cyber)
http.HandleFunc("/gitpage", GitPage)
http.HandleFunc("/gitpage/TicTacToe", GitPageTicTacToe)
http.HandleFunc("/trades", TradesHandler)
http.HandleFunc("/api/prices", PricesHandler)
fmt.Println("running on http://localhost:8081/")
if err := http.ListenAndServe(":8081", nil); err != nil {
log.Fatal(err)
}
}
// ---------- Trade type ----------
type Trade struct {
DataDiscriminator string `json:"data_discriminator"`
AssetCategory string `json:"asset_category"`
Currency string `json:"currency"`
Symbol string `json:"symbol"`
DateTime string `json:"date_time"`
Quantity string `json:"quantity"`
TradePrice string `json:"trade_price"`
CurrentPrice float64 `json:"current_price"` // enriched from EOD
Proceeds string `json:"proceeds"`
CommFee string `json:"comm_fee"`
Basis string `json:"basis"`
RealizedPL string `json:"realized_pl"`
MTMLPL string `json:"mtm_pl"`
Code string `json:"code"`
}
// ---------- Enrich logic ----------
func enrichRecords(records [][]string, priceDate time.Time) ([]Trade, error) {
// Collect unique tickers from Data/Order rows
type key struct{ symbol, currency string }
needed := map[key]struct{}{}
for _, row := range records {
if len(row) <= colCPrice {
continue
}
if row[colType] == "Trades" && row[colHeader] == "Data" && row[colDataDisc] == "Order" {
sym := strings.TrimSpace(row[colSymbol])
cur := strings.TrimSpace(row[colCurrency])
if t := toYahooTicker(sym, cur); t != "" {
needed[key{t, cur}] = struct{}{}
}
}
}
// Fetch prices concurrently
prices := map[string]float64{}
var mu sync.Mutex
var wg sync.WaitGroup
for k := range needed {
wg.Add(1)
go func(ticker string) {
defer wg.Done()
eod, err := FetchEOD(ticker, priceDate)
mu.Lock()
defer mu.Unlock()
if err != nil {
log.Printf("[warn] %s: %v", ticker, err)
} else {
prices[ticker] = eod.Close
}
}(k.symbol)
}
wg.Wait()
// Build response
var trades []Trade
for _, row := range records {
if len(row) <= colCPrice {
continue
}
if row[colType] != "Trades" || row[colHeader] != "Data" || row[colDataDisc] != "Order" {
continue
}
sym := strings.TrimSpace(row[colSymbol])
cur := strings.TrimSpace(row[colCurrency])
ticker := toYahooTicker(sym, cur)
var currentPrice float64
if ticker != "" {
currentPrice = prices[ticker]
}
t := Trade{
DataDiscriminator: strings.TrimSpace(row[colDataDisc]),
AssetCategory: strings.TrimSpace(row[colAssetCategory]),
Currency: cur,
Symbol: sym,
DateTime: safeCol(row, colDateTime),
Quantity: safeCol(row, colQuantity),
TradePrice: safeCol(row, colTPrice),
CurrentPrice: currentPrice,
Proceeds: safeCol(row, 10),
CommFee: safeCol(row, 11),
Basis: safeCol(row, 12),
RealizedPL: safeCol(row, 13),
MTMLPL: safeCol(row, 14),
Code: safeCol(row, 15),
}
trades = append(trades, t)
}
return trades, nil
}
func safeCol(row []string, i int) string {
if i < len(row) {
return strings.TrimSpace(row[i])
}
return ""
}
func lastWeekday(t time.Time) time.Time {
for t.Weekday() == time.Saturday || t.Weekday() == time.Sunday {
t = t.AddDate(0, 0, -1)
}
return t
}
const (
colType = 0
colHeader = 1
colDataDisc = 2
colAssetCategory = 3
colCurrency = 4
colSymbol = 5
colDateTime = 6
colQuantity = 7
colTPrice = 8
colCPrice = 9
)
var tickerOverrides = map[string]string{
"NOVOBc": "NOVO-B.CO",
}
func toYahooTicker(ibkrSymbol, currency string) string {
if override, ok := tickerOverrides[ibkrSymbol]; ok {
return override
}
if strings.Contains(ibkrSymbol, " ") {
return "" // skip options
}
return ibkrSymbol
}
type EODPrice struct {
Ticker string
Date string
Open float64
High float64
Low float64
Close float64
Currency string
LatestPrice float64 // regularMarketPrice from meta — most recent quote
LatestTime time.Time
PreviousClose float64
ChangePercent float64
}
func FetchEOD(ticker string, date time.Time) (*EODPrice, error) {
start := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, time.UTC)
end := start.Add(24 * time.Hour)
url := fmt.Sprintf(
"https://query1.finance.yahoo.com/v8/finance/chart/%s?period1=%d&period2=%d&interval=1d",
ticker, start.Unix(), end.Unix(),
)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", "Mozilla/5.0")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var payload struct {
Chart struct {
Result []struct {
Meta struct {
Currency string `json:"currency"`
RegularMarketPrice float64 `json:"regularMarketPrice"`
RegularMarketTime int64 `json:"regularMarketTime"`
ChartPreviousClose float64 `json:"chartPreviousClose"`
RegularMarketChangePercent float64 `json:"regularMarketChangePercent"`
} `json:"meta"`
Indicators struct {
Quote []struct {
Open []float64 `json:"open"`
High []float64 `json:"high"`
Low []float64 `json:"low"`
Close []float64 `json:"close"`
} `json:"quote"`
} `json:"indicators"`
} `json:"result"`
Error *struct {
Description string `json:"description"`
} `json:"error"`
} `json:"chart"`
}
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
return nil, err
}
if payload.Chart.Error != nil {
return nil, fmt.Errorf("yahoo: %s", payload.Chart.Error.Description)
}
if len(payload.Chart.Result) == 0 {
return nil, fmt.Errorf("no data for %s on %s", ticker, date.Format("2006-01-02"))
}
r := payload.Chart.Result[0]
q := r.Indicators.Quote[0]
if len(q.Close) == 0 || q.Close[0] == 0 {
return nil, fmt.Errorf("no close price for %s on %s", ticker, date.Format("2006-01-02"))
}
return &EODPrice{
Ticker: ticker,
Date: date.Format("2006-01-02"),
Open: q.Open[0],
High: q.High[0],
Low: q.Low[0],
Close: q.Close[0],
Currency: r.Meta.Currency,
LatestPrice: r.Meta.RegularMarketPrice,
LatestTime: time.Unix(r.Meta.RegularMarketTime, 0).UTC(),
PreviousClose: r.Meta.ChartPreviousClose,
ChangePercent: r.Meta.RegularMarketChangePercent,
}, nil
}
func PricesHandler(w http.ResponseWriter, r *http.Request) {
var req struct {
Tickers []string `json:"tickers"`
}
json.NewDecoder(r.Body).Decode(&req)
priceDate := lastWeekday(time.Now().UTC().AddDate(0, 0, -1))
var results []EODPrice
for _, t := range req.Tickers {
eod, err := FetchEOD(t, priceDate)
if err == nil {
results = append(results, *eod)
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(results)
}
+103
View File
@@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>About — Samantha Friis</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap" rel="stylesheet"/>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header>
<a href="/" class="back">← back</a>
<p class="tag">// about me</p>
<h1>Samantha<br/><span>Friis</span></h1>
<p class="header-sub">Denmark, Aalborg &nbsp;·&nbsp; samantha42.xyz</p>
</header>
<main>
<p class="summary">
Bachelor's student in Business Administration seeking an entry-level analyst or finance-related role while studying. Strong numerical skills, advanced Excel, and experience in company, risk, and financial analysis. Analytical, detail-oriented, and efficiency-focused.
</p>
<!-- Skills -->
<section>
<p class="section-label">// skills</p>
<div class="skills-list">
<div class="skill-row"><span>Excel</span> <div class="skill-bar-track"><div class="skill-bar-fill" style="width:85%;animation-delay:0.1s"></div></div></div>
<div class="skill-row"><span>Python</span> <div class="skill-bar-track"><div class="skill-bar-fill" style="width:75%;animation-delay:0.2s"></div></div></div>
<div class="skill-row"><span>SQL</span> <div class="skill-bar-track"><div class="skill-bar-fill" style="width:60%;animation-delay:0.3s"></div></div></div>
<div class="skill-row"><span>Go</span> <div class="skill-bar-track"><div class="skill-bar-fill" style="width:70%;animation-delay:0.4s"></div></div></div>
<div class="skill-row"><span>Git</span> <div class="skill-bar-track"><div class="skill-bar-fill" style="width:65%;animation-delay:0.5s"></div></div></div>
</div>
</section>
<!-- Experience -->
<section>
<p class="section-label">// experience</p>
<div class="exp-block">
<h2>Independent Equity Research</h2>
<p>Perform fundamental analysis using SEC filings (10-K, 10-Q) and earnings reports. Assess profitability, liquidity, risk factors, and long-term business sustainability. Handle incomplete or low-transparency information through structured cross-validation.</p>
</div>
<div class="exp-block">
<h2>Technical Tools & Automation</h2>
<p>Build lightweight backend tools to automate repetitive analytical and operational tasks. Use Python for data processing, validation, and workflow automation. Develop lightweight online services in Go, focusing on speed and reliability.</p>
</div>
</section>
<!-- Education -->
<section>
<p class="section-label">// education</p>
<div class="edu-item">
<span class="edu-year">2025 — now</span>
<div>
<p class="edu-title">Bachelor of Economics & Business Administration (HA)</p>
<p class="edu-sub">Aalborg University &nbsp;·&nbsp; Expected 2028</p>
</div>
</div>
<div class="edu-item">
<span class="edu-year">2020 — 2023</span>
<div>
<p class="edu-title">HTX Technical Upper Secondary Education</p>
<p class="edu-sub">Viden Gymnasier &nbsp;·&nbsp; Mathematics A, Biology B<br/>Study project: Mathematics & Technology</p>
</div>
</div>
</section>
<!-- Languages -->
<section>
<p class="section-label">// languages</p>
<div class="skills-list">
<div class="skill-row"><span>Danish</span> <div class="skill-bar-track"><div class="skill-bar-fill" style="width:100%;animation-delay:0.1s"></div></div></div>
<div class="skill-row"><span>English</span><div class="skill-bar-track"><div class="skill-bar-fill" style="width:80%;animation-delay:0.2s"></div></div></div>
</div>
</section>
<!-- Interests -->
<section>
<p class="section-label">// interests</p>
<div class="tags">
<span class="tag-pill">Gaming</span>
<span class="tag-pill">Board games</span>
<span class="tag-pill">Programming</span>
<span class="tag-pill">Financial markets</span>
</div>
</section>
</main>
<footer>
<a href="mailto:contact@samantha42.xyz">contact@samantha42.xyz</a>
<p class="footer-copy">&copy; 2026 — All rights reserved</p>
</footer>
</div>
</body>
</html>
+144
View File
@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Cybersecurity — Samantha Friis</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap" rel="stylesheet"/>
<link rel="stylesheet" href="styles.css"/>
</head>
<body>
<div class="container--narrow">
<header>
<a href="/" class="back">← back</a>
<p class="tag">// opinion</p>
<h1>The Weakest<br/>Link Is Always<br/><span>Human</span></h1>
<div class="byline">
<span>Samantha Friis</span>
<span></span>
<span>Cybersecurity</span>
</div>
</header>
<article>
<p class="lead">
Most breaches are not the result of a sophisticated exploit or a zero-day vulnerability. They are the result of a person clicking the wrong link, reusing a password, or trusting a phone call they should not have answered. Security is not primarily a technical problem. It is an organizational one.
</p>
<h2>Know What You Are Protecting</h2>
<p>Before you can build a security posture, you need to understand your data. Not all information carries the same weight. Customer financial records, internal credentials, and strategic planning documents are not the same as a public press release or a shared meeting agenda. Treating all data as equally sensitive wastes resources and creates compliance fatigue. Treating it all as equally safe is how companies end up in the news.</p>
<p>The first step is classification. Every organization should have a clear and enforced taxonomy for what they hold — what is critical, what is sensitive, and what is routine. Once data is classified, access decisions, storage choices, and transmission policies all become considerably more straightforward.</p>
<div class="classify-grid">
<div class="classify-card">
<p class="level level-critical">● Critical</p>
<h3>Restricted</h3>
<p>Credentials, financial records, legal documents, private keys. Encrypted at rest and in transit. Need-to-know access only. Audited on access.</p>
</div>
<div class="classify-card">
<p class="level level-high">● Sensitive</p>
<h3>Internal</h3>
<p>Strategy documents, personnel files, client data. Role-based access. Not to leave internal systems without explicit sign-off.</p>
</div>
<div class="classify-card">
<p class="level level-standard">● Standard</p>
<h3>General</h3>
<p>Shared documentation, public-facing content, operational guides. Normal access controls. No special handling required.</p>
</div>
</div>
<h2>Passwords Are Not a Personal Choice</h2>
<p>The idea that employees should be trusted to choose and manage their own passwords is one of the most persistent and destructive myths in corporate security. People are not good at passwords. They reuse them across services, they write them down, they choose predictable patterns, and they resist changing them when asked. This is not a character flaw — it is human nature operating against a task humans were never designed to do well.</p>
<p>The answer is a mandatory, company-wide password manager. Not recommended. Not optional. Mandatory. Every account created for company use lives in the vault. Credentials are generated by the manager — long, random, unique per service. No employee ever needs to know or remember most of them. Access to the vault itself is protected by a strong master passphrase and hardware-backed two-factor authentication.</p>
<blockquote>
A password an employee <span>cannot remember</span> is a password an attacker <span>cannot guess</span>.
</blockquote>
<h2>Two-Factor Is Not Optional</h2>
<p>Two-factor authentication is the single highest-return security investment any organization can make. A stolen password without the second factor is nearly useless. Yet an astonishing number of companies treat 2FA as something to enable "if employees want to." That framing gets it exactly backwards.</p>
<p>Every account with access to company systems requires 2FA, enforced at the policy level — not left to the individual. Preference should go to hardware security keys like <code>YubiKey</code> or authenticator apps generating time-based codes. SMS-based 2FA is better than nothing, but SIM-swap attacks make it the weakest option and it should be phased out wherever possible.</p>
<p>Enrollment should happen on day one, before access is granted to any system. Not after. The moment an employee has a credential without a second factor is a window of exposure that does not need to exist.</p>
<h2>Make Access Deliberately Hard</h2>
<p>This sounds counterintuitive. Friction is the enemy of productivity — until it is the thing standing between your database and someone who found a badge in the parking lot. The principle of least privilege is not just a compliance checkbox. It is an architecture decision.</p>
<p>Employees should have access to exactly what they need to do their job, and nothing more. Access should be reviewed on a schedule and revoked immediately upon role change or departure. Broad access to critical systems should require explicit justification, a second approver, and a time limit. The default answer to an access request is not yes — it is: why do you need this, for how long, and who else knows?</p>
<p>This friction is not about distrust. It is about reducing the blast radius of any single compromised account. If an attacker gains access to a junior analyst's credentials, they should find themselves in a narrow corridor, not a wide open floor.</p>
<h2>Social Engineering Is the Real Attack Surface</h2>
<p>Technical controls can be near-perfect and still fail the moment someone calls the help desk pretending to be a manager who has been locked out while traveling. Social engineering works because it exploits social instincts that are otherwise useful — the tendency to help, to defer to authority, to avoid confrontation.</p>
<p>Mitigating this requires two things: process and culture. Process means that no credential reset, no access grant, and no sensitive disclosure ever happens over a phone call or an email alone, regardless of how urgent it sounds or who the request appears to come from. Verification must follow a separate, pre-established channel. The louder someone insists the situation is urgent, the more carefully the process should be followed.</p>
<p>Culture means that employees feel safe slowing down and saying no. An employee who challenges an unusual request should be recognized for it, not made to feel obstructive. The organization needs to make it socially acceptable — even admirable — to be the person who held the line.</p>
<table class="attack-table">
<thead>
<tr>
<th>Attack Vector</th>
<th>Method</th>
<th>Countermeasure</th>
</tr>
</thead>
<tbody>
<tr>
<td>Phishing</td>
<td>Fraudulent email mimicking a trusted sender to harvest credentials or deploy malware</td>
<td class="counter">Email filtering, DMARC/DKIM, regular drill simulations, no credential entry from email links</td>
</tr>
<tr>
<td>Vishing</td>
<td>Voice call impersonating IT, management, or a vendor to extract access or trigger an action</td>
<td class="counter">Zero verbal credential resets. All requests verified via a separate known channel, no exceptions</td>
</tr>
<tr>
<td>Credential Stuffing</td>
<td>Automated use of leaked passwords from other breaches against company systems</td>
<td class="counter">Mandatory password manager with unique credentials per service. 2FA blocks the rest</td>
</tr>
<tr>
<td>Insider Threat</td>
<td>Disgruntled or compromised employee exfiltrating data or granting unauthorized access</td>
<td class="counter">Least-privilege access, access logging and anomaly alerts, prompt offboarding procedures</td>
</tr>
<tr>
<td>Pretexting</td>
<td>Fabricated scenario (new hire, auditor, vendor) to gain physical or digital access</td>
<td class="counter">Visitor protocols, escort requirements, no tailgating culture, ID verification for all physical access</td>
</tr>
</tbody>
</table>
<h2>Security Is a Continuous Practice</h2>
<p>A one-time audit followed by a certificate on the wall is not a security posture. It is a snapshot. Threat landscapes change, personnel changes, and systems change. Security needs to be a living practice — regular access reviews, periodic credential audits, tabletop exercises for breach scenarios, and honest post-mortems when something goes wrong.</p>
<p>The companies that handle breaches best are not necessarily the ones that prevented them — they are the ones that detected them fast, contained them well, and communicated clearly. That kind of response capability does not appear on the day of the incident. It is built over years of treating security as infrastructure, not an afterthought.</p>
<hr/>
<p>You cannot patch people. But you can build systems and cultures that make it very difficult for human error to become organizational catastrophe. Mandatory 2FA. Mandatory password managers. Minimal access by default. Verification processes that do not bend under social pressure. These are not sophisticated controls. They are disciplined ones — and discipline, consistently applied, is the closest thing to security that actually exists.</p>
</article>
<footer>
<a href="mailto:contact@samantha42.xyz">contact@samantha42.xyz</a>
<p class="footer-copy">&copy; 2026 — All rights reserved</p>
</footer>
</div>
</body>
</html>
+188
View File
@@ -0,0 +1,188 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Git — Samantha Friis</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap" rel="stylesheet"/>
<link rel="stylesheet" href="styles.css"/>
</head>
<body>
<div class="container">
<header>
<a href="/" class="back">← back</a>
<p class="tag">// git</p>
<h1>Code &<br/><span>Repositories</span></h1>
<p class="header-sub">Personal projects &nbsp;·&nbsp; Private & public</p>
</header>
<main class="two-col">
<!-- ── LEFT: Login / Private Dashboard ── -->
<div>
<p class="section-label">// private access</p>
<div class="login-panel" id="login-panel">
<h2>Sign In</h2>
<p class="desc">Access to private repositories and personal workspace. Requires credentials and 2FA.</p>
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" placeholder="samantha" autocomplete="username" />
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" placeholder="••••••••••••" autocomplete="current-password" />
</div>
<div class="twofa-group" id="twofa-group">
<label for="twofa">Authenticator Code</label>
<input type="text" id="twofa" placeholder="000 000" maxlength="7" autocomplete="one-time-code" />
<p class="twofa-hint">Enter the 6-digit code from your authenticator app.</p>
</div>
<button class="btn-login" id="login-btn" onclick="handleLogin()">Continue →</button>
<p class="login-error" id="login-error">Invalid credentials. Please try again.</p>
</div>
<!-- Private dashboard (shown after login) -->
<div id="private-dashboard">
<div class="repo-list">
<a href="#" class="repo-item">
<div>
<p class="repo-name">samantha-site</p>
<p class="repo-desc">Personal website and Go backend server</p>
</div>
<span class="repo-badge private">private</span>
</a>
<a href="#" class="repo-item">
<div>
<p class="repo-name">equity-tools</p>
<p class="repo-desc">Python scripts for SEC filing analysis and financial modeling</p>
</div>
<span class="repo-badge active">active</span>
</a>
<a href="#" class="repo-item">
<div>
<p class="repo-name">infra-config</p>
<p class="repo-desc">Dotfiles, server configs, and deployment scripts</p>
</div>
<span class="repo-badge private">private</span>
</a>
<a href="#" class="repo-item">
<div>
<p class="repo-name">typst-templates</p>
<p class="repo-desc">Custom Typst document templates for reports and analysis</p>
</div>
<span class="repo-badge active">active</span>
</a>
</div>
<button class="logout-btn" onclick="handleLogout()">← sign out</button>
</div>
</div>
<!-- ── RIGHT: Public section ── -->
<div>
<div class="repo-list">
<a href="/gitpage/TicTacToe" class="repo-item">
<div>
<p class="repo-name">TicTacToe</p>
<p class="repo-desc">Python learning tic tac toe game in terminal</p>
</div>
<span class="repo-badge private">private</span>
</a>
<a href="/gitpage/infra-config" class="repo-item">
<div>
<p class="repo-name">infra-config</p>
<p class="repo-desc">Dotfiles, server configs, and deployment scripts</p>
</div>
<span class="repo-badge private">private</span>
</a>
<a href="/gitpage/typst-templates" class="repo-item">
<div>
<p class="repo-name">typst-templates</p>
<p class="repo-desc">Custom Typst document templates for reports and analysis</p>
</div>
<span class="repo-badge active">active</span>
</a>
</div>
<button class="logout-btn" onclick="handleLogout()">← sign out</button>
</div>
</div>
</main>
<footer>
<a href="mailto:contact@samantha42.xyz">contact@samantha42.xyz</a>
<p class="footer-copy">&copy; 2026 — All rights reserved</p>
</footer>
</div>
<script>
let step = 1;
function handleLogin() {
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value.trim();
const twofa = document.getElementById('twofa').value.trim();
const tfGroup = document.getElementById('twofa-group');
document.getElementById('login-error').classList.remove('visible');
if (step === 1) {
if (!username || !password) { showError('Please enter your username and password.'); return; }
step = 2;
tfGroup.classList.add('visible');
document.getElementById('login-btn').textContent = 'Verify →';
document.getElementById('twofa').focus();
} else {
if (!twofa || twofa.replace(/\s/g, '').length < 6) { showError('Enter the 6-digit code from your authenticator app.'); return; }
if (twofa.replace(/\s/g, '').match(/^\d{6}$/)) {
showDashboard();
} else {
showError('Invalid authenticator code.');
}
}
}
function showError(msg) {
const error = document.getElementById('login-error');
const panel = document.getElementById('login-panel');
error.textContent = msg;
error.classList.add('visible');
panel.classList.add('shake');
setTimeout(() => panel.classList.remove('shake'), 500);
}
function showDashboard() {
document.getElementById('login-panel').style.display = 'none';
document.getElementById('private-dashboard').classList.add('visible');
}
function handleLogout() {
step = 1;
document.getElementById('login-panel').style.display = '';
document.getElementById('private-dashboard').classList.remove('visible');
document.getElementById('username').value = '';
document.getElementById('password').value = '';
document.getElementById('twofa').value = '';
document.getElementById('twofa-group').classList.remove('visible');
document.getElementById('login-btn').textContent = 'Continue →';
document.getElementById('login-error').classList.remove('visible');
}
document.addEventListener('keydown', (e) => { if (e.key === 'Enter') handleLogin(); });
document.getElementById('twofa').addEventListener('input', (e) => {
let val = e.target.value.replace(/\D/g, '').slice(0, 6);
if (val.length > 3) val = val.slice(0, 3) + ' ' + val.slice(3);
e.target.value = val;
});
</script>
</body>
</html>
+953
View File
@@ -0,0 +1,953 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>username / project-name</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,400&family=Syne:wght@400;700;800&display=swap" rel="stylesheet" />
<style>
/* ─── Variables ─── */
:root {
--bg: #252525;
--surface: #2b2b2b;
--surface2: #313131;
--border: #3a3a3a;
--border-focus: #4e4e4e;
--text: #e8e8e0;
--muted: #777770;
--accent: #B88FE0;
--accent2: #6247AA;
--green: #6fcf8a;
--red: #e07070;
--yellow: #e0c870;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
background: var(--bg);
color: var(--text);
font-family: 'DM Mono', monospace;
min-height: 100vh;
overflow-x: hidden;
}
/* Grain overlay */
body::before {
content: '';
position: fixed;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E");
pointer-events: none;
z-index: 999;
opacity: 0.35;
}
.container { max-width: 980px; margin: 0 auto; padding: 0 2rem; }
/* ─── Top nav ─── */
.topbar {
border-bottom: 1px solid var(--border);
padding: 1rem 0;
animation: fadeUp 0.4s ease both;
}
.topbar-inner {
display: flex;
align-items: center;
gap: 1.5rem;
}
.site-logo {
font-family: 'Syne', sans-serif;
font-weight: 800;
font-size: 1rem;
color: var(--text);
text-decoration: none;
letter-spacing: -0.02em;
}
.site-logo span { color: var(--accent); }
.breadcrumb {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: 0.78rem;
color: var(--muted);
}
.breadcrumb a {
color: var(--accent);
text-decoration: none;
transition: opacity 0.2s;
}
.breadcrumb a:hover { opacity: 0.7; }
.breadcrumb .sep { color: var(--border-focus); }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 1rem; }
.nav-link {
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
text-decoration: none;
transition: color 0.2s;
}
.nav-link:hover { color: var(--text); }
/* ─── Repo header ─── */
.repo-header {
padding: 2.5rem 0 2rem;
border-bottom: 1px solid var(--border);
animation: fadeUp 0.5s 0.05s ease both;
}
.repo-title-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 2rem;
flex-wrap: wrap;
}
.repo-title-group { flex: 1; min-width: 0; }
.repo-tag {
font-size: 0.65rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 0.6rem;
}
.repo-name {
font-family: 'Syne', sans-serif;
font-size: clamp(1.6rem, 3vw, 2.4rem);
font-weight: 800;
line-height: 1.05;
letter-spacing: -0.02em;
margin-bottom: 0.5rem;
}
.repo-name span { color: var(--accent); }
.repo-desc-text {
font-size: 0.8rem;
color: var(--muted);
line-height: 1.6;
max-width: 540px;
margin-bottom: 1rem;
}
.repo-topics {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
margin-bottom: 1rem;
}
.topic {
font-size: 0.62rem;
padding: 0.2rem 0.6rem;
border: 1px solid rgba(184, 143, 224, 0.2);
color: var(--accent);
letter-spacing: 0.08em;
background: rgba(184, 143, 224, 0.05);
transition: background 0.2s;
}
.topic:hover { background: rgba(184, 143, 224, 0.12); cursor: default; }
/* stats row */
.repo-stats {
display: flex;
gap: 1.5rem;
flex-wrap: wrap;
}
.stat-item {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: 0.72rem;
color: var(--muted);
}
.stat-item .stat-icon { font-size: 0.85rem; opacity: 0.6; }
.stat-item strong { color: var(--text); font-weight: 500; }
/* action buttons */
.repo-actions {
display: flex;
flex-direction: column;
gap: 0.5rem;
flex-shrink: 0;
}
.btn-primary {
background: var(--accent2);
color: var(--text);
font-family: 'DM Mono', monospace;
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 0.6rem 1.2rem;
border: none;
cursor: pointer;
transition: background 0.2s;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
}
.btn-primary:hover { background: var(--accent); }
.btn-ghost {
background: transparent;
color: var(--muted);
font-family: 'DM Mono', monospace;
font-size: 0.72rem;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 0.6rem 1.2rem;
border: 1px solid var(--border);
cursor: pointer;
transition: border-color 0.2s, color 0.2s;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
}
.btn-ghost:hover { border-color: var(--border-focus); color: var(--text); }
/* ─── Tab bar ─── */
.tabs {
border-bottom: 1px solid var(--border);
display: flex;
gap: 0;
animation: fadeUp 0.5s 0.1s ease both;
overflow-x: auto;
}
.tab {
font-size: 0.7rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
padding: 1rem 1.2rem;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 0.2s, border-color 0.2s;
white-space: nowrap;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.tab-count {
font-size: 0.6rem;
padding: 0.1rem 0.4rem;
border: 1px solid var(--border);
color: var(--muted);
}
/* ─── Main layout ─── */
.repo-body {
display: grid;
grid-template-columns: 1fr 260px;
gap: 2rem;
padding: 2rem 0 4rem;
animation: fadeUp 0.5s 0.15s ease both;
}
/* ─── File browser ─── */
.file-browser {
border: 1px solid var(--border);
}
.file-browser-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.8rem 1rem;
background: var(--surface);
border-bottom: 1px solid var(--border);
gap: 1rem;
}
.branch-selector {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.72rem;
color: var(--text);
background: var(--bg);
border: 1px solid var(--border);
padding: 0.35rem 0.7rem;
cursor: pointer;
transition: border-color 0.2s;
}
.branch-selector:hover { border-color: var(--border-focus); }
.branch-name { color: var(--accent); }
.file-meta {
font-size: 0.68rem;
color: var(--muted);
display: flex;
align-items: center;
gap: 0.8rem;
flex: 1;
}
.latest-commit {
font-size: 0.68rem;
color: var(--muted);
}
.latest-commit span { color: var(--text); }
.commit-hash {
font-size: 0.65rem;
color: var(--accent2);
border: 1px solid rgba(98, 71, 170, 0.3);
padding: 0.1rem 0.4rem;
cursor: pointer;
transition: background 0.2s;
}
.commit-hash:hover { background: rgba(98, 71, 170, 0.1); }
/* file rows */
.file-row {
display: grid;
grid-template-columns: 1.5rem 1fr 1fr auto;
align-items: center;
gap: 0.5rem;
padding: 0.65rem 1rem;
border-bottom: 1px solid var(--border);
transition: background 0.15s;
cursor: pointer;
text-decoration: none;
color: inherit;
}
.file-row:last-child { border-bottom: none; }
.file-row:hover { background: var(--surface2); }
.file-icon { font-size: 0.85rem; text-align: center; }
.file-name {
font-size: 0.78rem;
color: var(--text);
transition: color 0.15s;
}
.file-row:hover .file-name { color: var(--accent); }
.file-row.dir .file-name { color: var(--accent2); }
.file-row.dir:hover .file-name { color: var(--accent); }
.file-commit {
font-size: 0.65rem;
color: var(--muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-age {
font-size: 0.62rem;
color: var(--muted);
white-space: nowrap;
text-align: right;
}
/* README */
.readme {
border: 1px solid var(--border);
margin-top: 1rem;
}
.readme-bar {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.8rem 1rem;
background: var(--surface);
border-bottom: 1px solid var(--border);
font-size: 0.7rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
}
.readme-bar-icon { color: var(--accent); }
.readme-content {
padding: 2rem;
font-size: 0.82rem;
line-height: 1.9;
color: #c8c8c0;
}
.readme-content h1 {
font-family: 'Syne', sans-serif;
font-size: 1.4rem;
font-weight: 800;
color: var(--text);
margin-bottom: 0.8rem;
letter-spacing: -0.02em;
}
.readme-content h2 {
font-family: 'Syne', sans-serif;
font-size: 1rem;
font-weight: 700;
color: var(--accent);
margin: 2rem 0 0.8rem;
letter-spacing: -0.01em;
}
.readme-content h2::before {
content: '// ';
color: var(--accent2);
font-family: 'DM Mono', monospace;
font-weight: 300;
}
.readme-content p { margin-bottom: 1rem; }
.readme-content code {
font-family: 'DM Mono', monospace;
font-size: 0.76rem;
color: var(--accent);
background: rgba(184, 143, 224, 0.08);
padding: 0.1rem 0.4rem;
border: 1px solid rgba(184, 143, 224, 0.15);
}
.readme-content pre {
background: var(--surface);
border: 1px solid var(--border);
padding: 1.2rem 1.4rem;
margin: 1rem 0 1.4rem;
overflow-x: auto;
}
.readme-content pre code {
background: none;
border: none;
padding: 0;
font-size: 0.76rem;
color: #c8c8c0;
}
.readme-content ul {
list-style: none;
padding: 0;
margin: 0 0 1rem;
}
.readme-content ul li {
padding-left: 1.2rem;
position: relative;
margin-bottom: 0.4rem;
font-size: 0.8rem;
}
.readme-content ul li::before {
content: '▸';
position: absolute;
left: 0;
color: var(--accent2);
font-size: 0.7rem;
}
.readme-badge {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-size: 0.62rem;
padding: 0.2rem 0.6rem;
border: 1px solid var(--border);
color: var(--muted);
margin-right: 0.4rem;
margin-bottom: 0.8rem;
}
.readme-badge.green { color: var(--green); border-color: rgba(111, 207, 138, 0.3); }
.readme-badge.purple { color: var(--accent); border-color: rgba(184, 143, 224, 0.3); }
.readme-badge.yellow { color: var(--yellow); border-color: rgba(224, 200, 112, 0.3); }
/* ─── Sidebar ─── */
.sidebar { display: flex; flex-direction: column; gap: 1.5rem; }
.sidebar-section { }
.sidebar-title {
font-size: 0.62rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 0.8rem;
padding-bottom: 0.6rem;
border-bottom: 1px solid var(--border);
}
/* about */
.about-desc {
font-size: 0.75rem;
color: var(--muted);
line-height: 1.7;
margin-bottom: 1rem;
}
.about-link {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.72rem;
color: var(--accent);
text-decoration: none;
margin-bottom: 0.5rem;
transition: opacity 0.2s;
}
.about-link:hover { opacity: 0.7; }
/* languages */
.lang-bar {
height: 4px;
display: flex;
border-radius: 2px;
overflow: hidden;
margin-bottom: 0.8rem;
}
.lang-seg { height: 100%; transition: opacity 0.2s; }
.lang-seg:hover { opacity: 0.7; cursor: default; }
.lang-list { display: flex; flex-direction: column; gap: 0.5rem; }
.lang-item {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.7rem;
}
.lang-label { display: flex; align-items: center; gap: 0.5rem; color: var(--muted); }
.lang-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.lang-pct { color: var(--muted); }
/* activity */
.activity-graph {
display: grid;
grid-template-columns: repeat(26, 1fr);
gap: 2px;
}
.activity-col { display: flex; flex-direction: column; gap: 2px; }
.activity-cell {
width: 100%;
aspect-ratio: 1;
border-radius: 1px;
background: var(--surface2);
transition: background 0.2s;
}
.activity-cell:hover { opacity: 0.7; cursor: default; }
.activity-cell.l1 { background: rgba(98, 71, 170, 0.25); }
.activity-cell.l2 { background: rgba(98, 71, 170, 0.5); }
.activity-cell.l3 { background: rgba(184, 143, 224, 0.5); }
.activity-cell.l4 { background: var(--accent); }
.activity-label {
font-size: 0.6rem;
color: var(--muted);
margin-top: 0.5rem;
text-align: right;
}
/* contributors */
.contributors { display: flex; flex-direction: column; gap: 0.6rem; }
.contributor {
display: flex;
align-items: center;
gap: 0.7rem;
text-decoration: none;
transition: opacity 0.2s;
}
.contributor:hover { opacity: 0.7; }
.avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--surface2);
border: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-family: 'Syne', sans-serif;
font-weight: 700;
color: var(--accent);
flex-shrink: 0;
}
.contributor-info { flex: 1; }
.contributor-name { font-size: 0.73rem; color: var(--text); }
.contributor-commits { font-size: 0.62rem; color: var(--muted); }
/* releases */
.release-item {
display: flex;
align-items: flex-start;
gap: 0.8rem;
padding-bottom: 0.8rem;
border-bottom: 1px solid var(--border);
margin-bottom: 0.8rem;
}
.release-item:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
.release-tag-label {
font-size: 0.62rem;
padding: 0.2rem 0.5rem;
background: rgba(111, 207, 138, 0.1);
border: 1px solid rgba(111, 207, 138, 0.3);
color: var(--green);
white-space: nowrap;
flex-shrink: 0;
}
.release-info {}
.release-name { font-size: 0.75rem; color: var(--text); margin-bottom: 0.15rem; }
.release-date { font-size: 0.62rem; color: var(--muted); }
/* ─── Footer ─── */
footer {
padding: 2rem 0 2.5rem;
border-top: 1px solid var(--border);
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
text-align: center;
animation: fadeUp 0.6s 0.4s ease both;
}
.footer-copy { font-size: 0.65rem; color: var(--muted); letter-spacing: 0.05em; }
footer a { color: var(--accent); text-decoration: none; font-size: 0.75rem; transition: opacity 0.2s; }
footer a:hover { opacity: 0.7; }
/* ─── Animations ─── */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
/* ─── Responsive ─── */
@media (max-width: 768px) {
.repo-body { grid-template-columns: 1fr; }
.sidebar { order: -1; }
.repo-actions { flex-direction: row; flex-wrap: wrap; }
.file-row { grid-template-columns: 1.5rem 1fr auto; }
.file-commit { display: none; }
}
@media (max-width: 560px) {
.repo-title-row { flex-direction: column; }
.tabs { gap: 0; }
.tab { padding: 0.8rem 0.8rem; font-size: 0.65rem; }
}
</style>
</head>
<body>
<!-- Top nav -->
<nav class="topbar">
<div class="container">
<div class="topbar-inner">
<a href="/gitpage" class="site-logo">Git</a>
<div class="breadcrumb">
<a href="#">samantha</a>
<span class="sep">/</span>
<a href="#" style="font-weight:500;">TicTacToe</a>
</div>
<div class="topbar-right">
<a href="#" class="nav-link">Projects</a>
<a href="#" class="nav-link">About</a>
</div>
</div>
</div>
</nav>
<!-- Repo header -->
<div class="repo-header">
<div class="container">
<div class="repo-title-row">
<div class="repo-title-group">
<div class="repo-tag">// public repository</div>
<div class="repo-name">project-<span>name</span></div>
<div class="repo-desc-text">A fast, minimal toolkit for processing and visualizing time-series data. Built for clarity and performance with zero dependencies.</div>
<div class="repo-topics">
<span class="topic">typescript</span>
<span class="topic">data-viz</span>
<span class="topic">time-series</span>
<span class="topic">zero-deps</span>
<span class="topic">cli</span>
</div>
<div class="repo-stats">
<div class="stat-item">
<span class="stat-icon"></span>
<strong>248</strong> stars
</div>
<div class="stat-item">
<span class="stat-icon"></span>
<strong>34</strong> forks
</div>
<div class="stat-item">
<span class="stat-icon"></span>
<strong>12</strong> watching
</div>
<div class="stat-item">
<span class="stat-icon"></span>
MIT License
</div>
</div>
</div>
<div class="repo-actions">
<a href="#" class="btn-primary">⬇ Clone</a>
<a href="#" class="btn-ghost">☆ Star</a>
<a href="#" class="btn-ghost">⑂ Fork</a>
</div>
</div>
</div>
</div>
<!-- Tabs -->
<div class="tabs">
<div class="container" style="display:flex;">
<a href="#" class="tab active">
◫ Code
</a>
<a href="#" class="tab">
◎ Issues <span class="tab-count">7</span>
</a>
<a href="#" class="tab">
⇄ Pull Requests <span class="tab-count">2</span>
</a>
<a href="#" class="tab">
◷ Actions
</a>
<a href="#" class="tab">
◈ Releases <span class="tab-count">4</span>
</a>
</div>
</div>
<!-- Main body -->
<main>
<div class="container">
<div class="repo-body">
<!-- Left: file browser + readme -->
<div>
<!-- File browser -->
<div class="file-browser">
<div class="file-browser-bar">
<div class="branch-selector">
<span class="branch-name">main</span>
</div>
<div class="file-meta">
<span class="latest-commit"><span>fix: handle edge case in parser</span></span>
<span class="commit-hash">a3f92d1</span>
</div>
<div style="font-size:0.65rem;color:var(--muted);white-space:nowrap;">3 days ago</div>
</div>
<!-- Dir rows -->
<a href="#" class="file-row dir">
<span class="file-icon">📁</span>
<span class="file-name">src</span>
<span class="file-commit">refactor: split core modules into separate files</span>
<span class="file-age">2 wks ago</span>
</a>
<a href="#" class="file-row dir">
<span class="file-icon">📁</span>
<span class="file-name">tests</span>
<span class="file-commit">test: add coverage for edge cases</span>
<span class="file-age">3 days ago</span>
</a>
<a href="#" class="file-row dir">
<span class="file-icon">📁</span>
<span class="file-name">docs</span>
<span class="file-commit">docs: update API reference</span>
<span class="file-age">1 wk ago</span>
</a>
<a href="#" class="file-row dir">
<span class="file-icon">📁</span>
<span class="file-name">examples</span>
<span class="file-commit">chore: add more usage examples</span>
<span class="file-age">5 days ago</span>
</a>
<a href="#" class="file-row">
<span class="file-icon">📄</span>
<span class="file-name">.gitignore</span>
<span class="file-commit">chore: initial setup</span>
<span class="file-age">3 mos ago</span>
</a>
<a href="#" class="file-row">
<span class="file-icon">📄</span>
<span class="file-name">LICENSE</span>
<span class="file-commit">chore: initial setup</span>
<span class="file-age">3 mos ago</span>
</a>
<a href="#" class="file-row">
<span class="file-icon">📄</span>
<span class="file-name">README.md</span>
<span class="file-commit">docs: improve getting started section</span>
<span class="file-age">1 wk ago</span>
</a>
<a href="#" class="file-row">
<span class="file-icon">📄</span>
<span class="file-name">package.json</span>
<span class="file-commit">chore: bump dependencies</span>
<span class="file-age">4 days ago</span>
</a>
<a href="#" class="file-row">
<span class="file-icon">📄</span>
<span class="file-name">tsconfig.json</span>
<span class="file-commit">chore: strict mode enabled</span>
<span class="file-age">2 mos ago</span>
</a>
</div>
<!-- README -->
<div class="readme">
<div class="readme-bar">
<span class="readme-bar-icon"></span>
README.md
</div>
<div class="readme-content">
<span class="readme-badge green">● build passing</span>
<span class="readme-badge purple">v2.1.0</span>
<span class="readme-badge yellow">MIT</span>
<h1>project-name</h1>
<p>A fast, minimal toolkit for processing and visualizing time-series data. Built with TypeScript, zero runtime dependencies, works in Node and the browser.</p>
<h2>Installation</h2>
<pre><code>npm install project-name
# or
yarn add project-name</code></pre>
<h2>Quick Start</h2>
<pre><code>import { TimeSeries, plot } from 'project-name';
const ts = new TimeSeries(data);
const smoothed = ts.rollingMean(7);
plot(smoothed, { width: 80, height: 20 });</code></pre>
<h2>Features</h2>
<ul>
<li>Zero runtime dependencies — ships as a single ESM/CJS bundle</li>
<li>Rolling statistics: mean, median, stddev, percentiles</li>
<li>Gap detection and interpolation for irregular time series</li>
<li>ASCII plot renderer for terminal output</li>
<li>Full TypeScript support with strict types</li>
<li>~8kb gzipped</li>
</ul>
<h2>Documentation</h2>
<p>Full API reference available in <code>docs/</code> or at the project website. Each method is documented with examples and edge-case notes.</p>
</div>
</div>
</div>
<!-- Sidebar -->
<aside class="sidebar">
<!-- About -->
<div class="sidebar-section">
<div class="sidebar-title">About</div>
<div class="about-desc">Fast, minimal time-series processing and visualization. Zero deps.</div>
<a href="#" class="about-link">🔗 project-name.dev</a>
<a href="#" class="about-link">📦 npm package</a>
</div>
<!-- Languages -->
<div class="sidebar-section">
<div class="sidebar-title">Languages</div>
<div class="lang-bar">
<div class="lang-seg" style="width:74%;background:var(--accent2);"></div>
<div class="lang-seg" style="width:16%;background:var(--accent);"></div>
<div class="lang-seg" style="width:7%;background:var(--yellow);"></div>
<div class="lang-seg" style="width:3%;background:var(--muted);"></div>
</div>
<div class="lang-list">
<div class="lang-item">
<div class="lang-label"><div class="lang-dot" style="background:var(--accent2);"></div>TypeScript</div>
<div class="lang-pct">74%</div>
</div>
<div class="lang-item">
<div class="lang-label"><div class="lang-dot" style="background:var(--accent);"></div>JavaScript</div>
<div class="lang-pct">16%</div>
</div>
<div class="lang-item">
<div class="lang-label"><div class="lang-dot" style="background:var(--yellow);"></div>Shell</div>
<div class="lang-pct">7%</div>
</div>
<div class="lang-item">
<div class="lang-label"><div class="lang-dot" style="background:var(--muted);"></div>Other</div>
<div class="lang-pct">3%</div>
</div>
</div>
</div>
<!-- Releases -->
<div class="sidebar-section">
<div class="sidebar-title">Releases</div>
<div class="release-item">
<span class="release-tag-label">v2.1.0</span>
<div class="release-info">
<div class="release-name">Rolling stats & fix</div>
<div class="release-date">5 days ago</div>
</div>
</div>
<div class="release-item">
<span class="release-tag-label">v2.0.0</span>
<div class="release-info">
<div class="release-name">Major rewrite</div>
<div class="release-date">3 wks ago</div>
</div>
</div>
<div class="release-item">
<span class="release-tag-label">v1.4.2</span>
<div class="release-info">
<div class="release-name">Bug fix release</div>
<div class="release-date">2 mos ago</div>
</div>
</div>
</div>
<!-- Activity -->
<div class="sidebar-section">
<div class="sidebar-title">Activity</div>
<div class="activity-graph" id="activityGraph"></div>
<div class="activity-label">Last 26 weeks</div>
</div>
<!-- Contributors -->
<div class="sidebar-section">
<div class="sidebar-title">Contributors</div>
<div class="contributors">
<a href="#" class="contributor">
<div class="avatar">U</div>
<div class="contributor-info">
<div class="contributor-name">username</div>
<div class="contributor-commits">142 commits</div>
</div>
</a>
<a href="#" class="contributor">
<div class="avatar">J</div>
<div class="contributor-info">
<div class="contributor-name">jsmith</div>
<div class="contributor-commits">28 commits</div>
</div>
</a>
<a href="#" class="contributor">
<div class="avatar">K</div>
<div class="contributor-info">
<div class="contributor-name">k_dev</div>
<div class="contributor-commits">11 commits</div>
</div>
</a>
</div>
</div>
</aside>
</div>
</div>
</main>
<footer>
<div class="container">
<a href="#">username</a>
<div class="footer-copy">© 2026 — built with intention</div>
</div>
</footer>
<script>
// Generate activity graph
const graph = document.getElementById('activityGraph');
const levels = [null, 'l1', 'l2', 'l3', 'l4'];
const pattern = [0,0,1,0,2,1,0,1,3,2,1,0,0,1,2,3,4,2,1,0,1,2,1,0,3,2];
for (let w = 0; w < 26; w++) {
const col = document.createElement('div');
col.className = 'activity-col';
for (let d = 0; d < 7; d++) {
const cell = document.createElement('div');
const activity = Math.random() < 0.4 ? Math.min(4, Math.floor(Math.random() * 3) + pattern[w]) : 0;
cell.className = 'activity-cell' + (activity > 0 ? ` l${activity}` : '');
col.appendChild(cell);
}
graph.appendChild(col);
}
// Tab switching
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', e => {
e.preventDefault();
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
});
});
</script>
</body>
</html>
+72
View File
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Home</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap" rel="stylesheet"/>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header>
<p class="tag">// personal space</p>
<h1>Hello,<br/>Friend<span>.</span></h1>
<p class="subtitle">Developer, tinkerer, investor. This is my little corner of the internet.</p>
</header>
<nav>
<div class="nav-grid">
<a href="/about" class="nav-item">
<p class="nav-number">01 —</p>
<p class="nav-title">About Me <span class="arrow"></span></p>
<p class="nav-desc">Who I am, what I'm working on, and how to get in touch.</p>
</a>
<a href="/portfolio" class="nav-item">
<p class="nav-number">02 —</p>
<p class="nav-title">Portfolio <span class="arrow"></span></p>
<p class="nav-desc">Investment thesis, positions, and market observations.</p>
</a>
<a href="/gitpage" target="_blank" rel="noopener" class="nav-item">
<p class="nav-number">03 —</p>
<p class="nav-title">Git <span class="arrow"></span></p>
<p class="nav-desc">Personal repositories, open-source projects and code experiments.</p>
</a>
<a href="/Klacq ApS" class="nav-item">
<p class="nav-number">04 —</p>
<p class="nav-title">Klacq ApS<span class="arrow"></span></p>
<p class="nav-desc">Small personaly owned streaming service</p>
</a>
<a href="/infra" class="nav-item">
<p class="nav-number">05 —</p>
<p class="nav-title">Infrastructure <span class="arrow"></span></p>
<p class="nav-desc">Computer setups for company efficiency </p>
</a>
<a href="/cyber" class="nav-item">
<p class="nav-number">06 —</p>
<p class="nav-title">Cyber Security<span class="arrow"></span></p>
<p class="nav-desc">Protect data, personal and companies.</p>
</a>
</div>
</nav>
<footer>
<a href="mailto:contact@samantha42.xyz">contact@samantha42.xyz</a>
<p class="footer-copy">&copy; 2026 — All rights reserved</p>
</footer>
</div>
</body>
</html>
+125
View File
@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Infrastructure — Samantha Friis</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap" rel="stylesheet"/>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header>
<a href="/" class="back">← back</a>
<p class="tag">// opinion</p>
<h1>Convenience<br/>Is Making Us<br/><span>Worse</span></h1>
<div class="byline">
<span>Samantha Friis</span>
<span></span>
<span>Computer Infrastructure</span>
</div>
</header>
<article>
<p class="lead">
The tools a company chooses to build on top of says a lot about how serious it is about the craft. The industry-wide drift toward Word, Teams, and Microsoft 365 is not a productivity upgrade — it is a slow erosion of competence dressed up as convenience.
</p>
<h2>The Convenience Trap</h2>
<p>
Every tool that hides complexity from you takes something away. When a new hire can open a laptop running Windows, install Office in ten minutes, and be "operational" by noon, that feels like a win. But operational is not the same as capable. What that person has actually learned is how to navigate a UI. They have not learned how their machine works, how their data flows, or how to adapt when the abstraction breaks.
</p>
<p>
Large vendor ecosystems like Microsoft 365 are engineered for the lowest common denominator. They are excellent at onboarding someone with no background — and they stop there. The ceiling is built in by design. You cannot meaningfully extend Word. You cannot script Teams in any serious way. You are a tenant inside someone else's system, paying monthly for the privilege of not understanding what is happening underneath you.
</p>
<blockquote>
Convenience optimizes for the speed of the <span>newcomer</span>.<br/>
It does not care about the ceiling of the <span>expert</span>.
</blockquote>
<h2>What We Trade Away</h2>
<p>
The deeper issue is what gets lost over time. A team that has never had to configure anything has never had to understand anything. When your document pipeline is Word plus SharePoint, you never learn what a proper typesetting system looks like. When your servers are Azure with a click-through UI, you never develop intuition for networking, permissions, or failure modes. The knowledge simply does not accumulate.
</p>
<p>
The companies most dependent on big vendor stacks also tend to be the most fragile. A license change, a price increase, a deprecated feature — and suddenly nobody knows what to do. There is no internal knowledge to fall back on. The stack was always a rented abstraction.
</p>
<div class="compare">
<div class="compare-col bad">
<h3>Vendor-Locked Stack</h3>
<ul>
<li>Fast to start, hard to master</li>
<li>Knowledge lives in the UI, not the team</li>
<li>Expensive at scale, locked by contracts</li>
<li>No meaningful customization</li>
<li>Fragile when the vendor changes terms</li>
<li>Homogeneous — everyone works the same way</li>
</ul>
</div>
<div class="compare-col good">
<h3>Custom Infrastructure</h3>
<ul>
<li>Slower to start, no ceiling</li>
<li>Knowledge compounds inside the team</li>
<li>Cheap at scale, you own what you build</li>
<li>Configured precisely for your workflow</li>
<li>Resilient — you understand every layer</li>
<li>Personal — the environment fits the person</li>
</ul>
</div>
</div>
<h2>Linux as a Philosophy</h2>
<p>
A Linux desktop is not just a different operating system. It is a different relationship with your machine. You configure your window manager, your keybindings, your terminal, your dotfiles. The environment becomes an extension of how you think. Two engineers on the same team can have entirely different setups that are both optimized for their own cognitive style — and that is a feature, not a problem.
</p>
<p>
This is not about being difficult or contrarian. It is about the fact that a person who has spent time configuring <code>neovim</code>, managing a <code>systemd</code> service, or writing their own shell scripts has fundamentally more understanding of computation than someone who has spent the same time clicking through GUIs. That understanding compounds. It transfers across every problem they touch.
</p>
<h2>Documents Should Be Code</h2>
<p>
The same logic applies to document production. Word processing is a GUI wrapped around formatting decisions you did not make. The output is inconsistent, the versioning is painful, and the result is never quite right. A typesetting system like <code>Typst</code> treats a document as source code — deterministic, version-controlled, composable. You write a template once and it is correct everywhere, forever. The document becomes a reproducible artifact.
</p>
<p>
Custom pipelines like this require an upfront investment. You have to learn the tool, build the template, establish the workflow. But once built, the leverage is enormous. Reports, invoices, analysis documents — all generated cleanly from structured data, with zero manual formatting. That is not possible in Word. It is not even imaginable in Word.
</p>
<h2>Own Your Servers</h2>
<p>
Self-hosted infrastructure demands that someone in your organization understands it. That is not a burden — it is the point. When you run your own services, you know exactly where your data lives, what it costs to run, and what happens when something fails. You build operational knowledge that no managed cloud service can give you.
</p>
<p>
This does not mean rejecting cloud entirely. It means being intentional. Use the cloud where the economics clearly favor it. Build and operate everything else yourself. The goal is a team that could reconstruct its own stack from scratch if it had to — because a team capable of that is a team that actually understands what it has built.
</p>
<hr/>
<p>
The best infrastructure is not the most convenient. It is the most understood. Tools that demand something from you — configuration, learning, patience — are the tools that make you better over time. Convenience is a ceiling. Complexity, embraced deliberately, is a floor that keeps getting higher.
</p>
</article>
<footer>
<a href="mailto:contact@samantha42.xyz">contact@samantha42.xyz</a>
<p class="footer-copy">&copy; 2026 — All rights reserved</p>
</footer>
</div>
</body>
</html>
+589
View File
@@ -0,0 +1,589 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Portfolio — Samantha Friis</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap" rel="stylesheet"/>
<link rel="stylesheet" href="styles.css">
<style>
/* ── Trades accordion ── */
.trades-section {
margin-top: 2.5rem;
}
.trades-toggle {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
background: none;
border: none;
border-top: 1px solid var(--border, #2a2a3a);
border-bottom: 1px solid var(--border, #2a2a3a);
width: 100%;
padding: 0.85rem 0;
color: inherit;
font-family: 'DM Mono', monospace;
font-size: 0.75rem;
letter-spacing: 0.07em;
text-align: left;
user-select: none;
}
.trades-toggle:hover .toggle-label {
color: var(--accent, #f59e0b);
}
.toggle-label {
color: var(--muted, #475569);
transition: color 0.15s;
}
.toggle-label span {
color: var(--accent, #f59e0b);
}
.toggle-icon {
color: var(--muted, #475569);
font-size: 1rem;
transition: transform 0.25s ease;
line-height: 1;
}
.trades-toggle[aria-expanded="true"] .toggle-icon {
transform: rotate(180deg);
}
.trades-body {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.3s ease;
}
.trades-body.open {
grid-template-rows: 1fr;
}
.trades-inner {
overflow: hidden;
}
.trades-filter {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
padding: 1rem 0 0.75rem;
}
.filter-btn {
background: none;
border: 1px solid var(--border, #2a2a3a);
border-radius: 4px;
color: var(--muted, #475569);
font-family: 'DM Mono', monospace;
font-size: 0.7rem;
letter-spacing: 0.06em;
padding: 3px 10px;
cursor: pointer;
transition: all 0.15s;
}
.filter-btn:hover,
.filter-btn.active {
border-color: var(--accent, #f59e0b);
color: var(--accent, #f59e0b);
background: rgba(245,158,11,0.06);
}
.trades-table-wrap {
overflow-x: auto;
padding-bottom: 1.5rem;
}
.trades-table {
width: 100%;
border-collapse: collapse;
font-size: 0.78rem;
}
.trades-table thead tr {
border-bottom: 1px solid var(--border, #2a2a3a);
}
.trades-table th {
color: var(--muted, #475569);
font-weight: 400;
letter-spacing: 0.07em;
font-size: 0.68rem;
padding: 0.5rem 0.75rem;
text-align: left;
white-space: nowrap;
}
.trades-table td {
padding: 0.65rem 0.75rem;
border-bottom: 1px solid rgba(42,42,58,0.5);
white-space: nowrap;
color: var(--text, #e2e8f0);
}
.trades-table tbody tr:last-child td {
border-bottom: none;
}
.trades-table tbody tr:hover td {
background: rgba(255,255,255,0.02);
}
.trades-table tr.hidden-row {
display: none;
}
.dir-buy { color: #4ade80; }
.dir-sell { color: #f87171; }
.pnl-pos { color: #4ade80; }
.pnl-neg { color: #f87171; }
.pnl-zero{ color: var(--muted, #475569); }
.trade-code {
display: inline-block;
padding: 1px 6px;
border-radius: 3px;
font-size: 0.65rem;
background: rgba(42,42,58,0.8);
color: var(--muted, #475569);
letter-spacing: 0.05em;
}
.trade-ticker {
font-weight: 500;
color: #f1f5f9;
}
.trades-count {
color: var(--muted, #475569);
font-size: 0.68rem;
padding: 0 0 0.5rem;
}
/* ── Live price styles ── */
.price-cell {
position: relative;
}
.price-val {
transition: color 0.4s;
}
.price-val.loading {
color: var(--muted, #475569);
}
.price-val.flash-up {
color: #4ade80;
}
.price-val.flash-down {
color: #f87171;
}
.price-status {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.68rem;
color: var(--muted, #475569);
margin-left: 0.5rem;
vertical-align: middle;
}
.price-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--muted, #475569);
display: inline-block;
}
.price-dot.live { background: #4ade80; box-shadow: 0 0 4px #4ade80; }
.price-dot.error { background: #f87171; }
.price-dot.loading {
background: #f59e0b;
animation: pulse 1s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.refresh-btn {
background: none;
border: 1px solid var(--border, #2a2a3a);
border-radius: 4px;
color: var(--muted, #475569);
font-family: 'DM Mono', monospace;
font-size: 0.68rem;
padding: 2px 8px;
cursor: pointer;
letter-spacing: 0.05em;
transition: all 0.15s;
}
.refresh-btn:hover {
border-color: var(--accent, #f59e0b);
color: var(--accent, #f59e0b);
}
.section-header-row {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.6rem;
}
</style>
</head>
<body>
<div class="container">
<header>
<a href="/" class="back">← back</a>
<p class="tag">// portfolio</p>
<h1>Investment<br/><span>Portfolio</span></h1>
<p class="header-sub">Equity positions &nbsp;·&nbsp; Personal research</p>
</header>
<!-- Summary cards -->
<div class="cards">
<div class="card">
<p class="card-label">Total Weight</p>
<p class="card-value neutral">100,10%</p>
</div>
<div class="card">
<p class="card-label">Weighted Return</p>
<p class="card-value up">+7,84%</p>
</div>
<div class="card">
<p class="card-label">Price Return (w)</p>
<p class="card-value up">+7,73%</p>
</div>
<div class="card">
<p class="card-label">Positions</p>
<p class="card-value neutral">5</p>
</div>
</div>
<!-- Invested table -->
<div class="section-header-row">
<p class="section-label" style="margin:0">// invested positions</p>
<span class="price-status" id="priceStatus">
<span class="price-dot loading" id="priceDot"></span>
<span id="priceStatusText">fetching prices…</span>
</span>
<button class="refresh-btn" onclick="fetchHoldingPrices()">↺ refresh</button>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Ticker</th>
<th>Market Price</th>
<th>Avg Price</th>
<th>Shares Out.</th>
<th>Mkt Cap (M)</th>
<th>Weight</th>
<th>Return Rate</th>
<th>Price Return</th>
<th>Rate × Weight</th>
<th>P.Return × Weight</th>
</tr>
</thead>
<tbody>
<tr data-ticker="MAERSK-B.CO">
<td><span class="ticker">MEARSK</span><span class="currency-badge">DKK</span></td>
<td class="price-cell"><span class="price-val" id="price-MAERSK-B.CO">15,270.00</span></td><td>12,414</td><td>15,224,309</td><td>232,475</td>
<td><div class="weight-cell">45.95%<div class="weight-bar-track"><div class="weight-bar-fill" style="width:46%"></div></div></div></td>
<td class="neutral">9%</td><td class="up">23.01%</td><td>4.17%</td><td class="up">10.57%</td>
</tr>
<tr data-ticker="DFDS.CO">
<td><span class="ticker">DFDS</span><span class="currency-badge">DKK</span></td>
<td class="price-cell"><span class="price-val" id="price-DFDS.CO">98.80</span></td><td>89.82</td><td>53,892,216</td><td>5,325</td>
<td><div class="weight-cell">10.43%<div class="weight-bar-track"><div class="weight-bar-fill" style="width:10%"></div></div></div></td>
<td class="neutral">8%</td><td class="up">10.00%</td><td>0.83%</td><td class="up">1.04%</td>
</tr>
<tr data-ticker="ORSTED.CO">
<td><span class="ticker">ORSTED</span><span class="currency-badge">DKK</span></td>
<td class="price-cell"><span class="price-val" id="price-ORSTED.CO">152.00</span></td><td>128.80</td><td>756,088,069</td><td>114,925</td>
<td><div class="weight-cell">9.17%<div class="weight-bar-track"><div class="weight-bar-fill" style="width:9%"></div></div></div></td>
<td class="neutral">3.25%</td><td class="up">18.01%</td><td>0.30%</td><td class="up">1.65%</td>
</tr>
<tr data-ticker="CSIQ">
<td><span class="ticker">CSIQ</span><span class="currency-badge">USD</span></td>
<td class="price-cell"><span class="price-val" id="price-CSIQ">20.74</span></td><td>20.53</td><td>67,620,463</td><td>1,402</td>
<td><div class="weight-cell">3.09%<div class="weight-bar-track"><div class="weight-bar-fill" style="width:3%"></div></div></div></td>
<td class="neutral">4.95%</td><td class="up">1.02%</td><td>0.15%</td><td class="up">0.03%</td>
</tr>
<tr data-ticker="NOVO-B.CO">
<td><span class="ticker">NOVO NORDISK</span><span class="currency-badge">DKK</span></td>
<td class="price-cell"><span class="price-val" id="price-NOVO-B.CO">251.00</span></td><td>305.00</td><td>4,442,064,180</td><td>1,114,958</td>
<td><div class="weight-cell">31.46%<div class="weight-bar-track"><div class="weight-bar-fill" style="width:31%"></div></div></div></td>
<td class="neutral">8%</td><td class="down">17.70%</td><td>2.38%</td><td class="down">5.57%</td>
</tr>
</tbody>
<tbody>
<tr><td colspan="10" class="watchlist-label">// watchlist — not invested</td></tr>
<tr class="watchlist-row">
<td><span class="ticker" style="color:var(--muted)">COST</span><span class="currency-badge">USD</span></td>
<td>978.00</td><td></td><td></td><td></td>
<td><div class="weight-cell"></div></td>
<td></td><td></td><td></td><td></td>
</tr>
</tbody>
</table>
</div>
<!-- ── Trades accordion ── -->
<div class="trades-section">
<button class="trades-toggle" aria-expanded="false" onclick="toggleTrades(this)">
<span class="toggle-label">// <span>trade history</span> — all executed orders</span>
<span class="toggle-icon"></span>
</button>
<div class="trades-body" id="tradesBody">
<div class="trades-inner">
<!-- Filter buttons -->
<div class="trades-filter">
<button class="filter-btn active" onclick="filterTrades('all', this)">All</button>
<button class="filter-btn" onclick="filterTrades('NOVOBc', this)">NOVOBc</button>
<button class="filter-btn" onclick="filterTrades('CSIQ', this)">CSIQ</button>
<button class="filter-btn" onclick="filterTrades('PYPL', this)">PYPL</button>
<button class="filter-btn" onclick="filterTrades('options', this)">Options</button>
</div>
<p class="trades-count" id="tradesCount">10 trades</p>
<div class="trades-table-wrap">
<table class="trades-table">
<thead>
<tr>
<th>Symbol</th>
<th>Asset</th>
<th>Ccy</th>
<th>Date / Time</th>
<th>Dir</th>
<th>Qty</th>
<th>Trade Price</th>
<th>Curr. Price</th>
<th>Proceeds</th>
<th>Comm/Fee</th>
<th>Basis</th>
<th>Realized P/L</th>
<th>MTM P/L</th>
<th>Code</th>
</tr>
</thead>
<tbody id="tradesBody_rows">
<!-- NOVOBc -->
<tr data-sym="NOVOBc">
<td><span class="trade-ticker">NOVOBc</span></td>
<td>Stocks</td><td>DKK</td>
<td>2026-02-24 03:05</td>
<td class="dir-buy">BUY</td>
<td>8</td><td>241.70</td><td>243.65</td>
<td>1,933.60</td><td>10.00</td><td>1,943.60</td>
<td class="pnl-zero">0.00</td><td class="pnl-pos">+15.60</td>
<td><span class="trade-code">O</span></td>
</tr>
<tr data-sym="NOVOBc">
<td><span class="trade-ticker">NOVOBc</span></td>
<td>Stocks</td><td>DKK</td>
<td>2026-02-25 07:10</td>
<td class="dir-buy">BUY</td>
<td>3</td><td>239.50</td><td>238.40</td>
<td>718.50</td><td>10.00</td><td>728.50</td>
<td class="pnl-zero">0.00</td><td class="pnl-neg">3.30</td>
<td><span class="trade-code">O</span></td>
</tr>
<!-- CSIQ -->
<tr data-sym="CSIQ">
<td><span class="trade-ticker">CSIQ</span></td>
<td>Stocks</td><td>USD</td>
<td>2026-01-20 14:33</td>
<td class="dir-buy">BUY</td>
<td>8</td><td>20.48</td><td>20.51</td>
<td>163.84</td><td>0.38</td><td>164.22</td>
<td class="pnl-zero">0.00</td><td class="pnl-pos">+0.24</td>
<td><span class="trade-code">O</span></td>
</tr>
<!-- PYPL -->
<tr data-sym="PYPL">
<td><span class="trade-ticker">PYPL</span></td>
<td>Stocks</td><td>USD</td>
<td>2026-01-20 14:31</td>
<td class="dir-sell">SELL</td>
<td>3</td><td>55.18</td><td>55.08</td>
<td>165.53</td><td>0.35</td><td>187.91</td>
<td class="pnl-neg">22.73</td><td class="pnl-pos">+0.29</td>
<td><span class="trade-code">C</span></td>
</tr>
<!-- Options -->
<tr data-sym="options">
<td><span class="trade-ticker">RGTI 15JAN27 5.5 P</span></td>
<td>Options</td><td>USD</td>
<td>2026-02-24 09:34</td>
<td class="dir-buy">BUY</td>
<td>1</td><td>0.53</td><td>0.5271</td>
<td>53.00</td><td>1.05</td><td>54.05</td>
<td class="pnl-zero">0.00</td><td class="pnl-neg">0.29</td>
<td><span class="trade-code">O</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<footer>
<a href="mailto:contact@samantha42.xyz">contact@samantha42.xyz</a>
<p class="footer-copy">&copy; 2026 — All rights reserved</p>
</footer>
</div>
<script>
// ── Holdings price fetch ──
// Calls the Go backend's /api/prices endpoint with a list of tickers.
// Falls back to FetchEOD-style Yahoo Finance if the backend isn't available.
const HOLDINGS = [
{ ticker: 'MAERSK-B.CO', label: 'MEARSK' },
{ ticker: 'DFDS.CO', label: 'DFDS' },
{ ticker: 'ORSTED.CO', label: 'ORSTED' },
{ ticker: 'CSIQ', label: 'CSIQ' },
{ ticker: 'NOVO-B.CO', label: 'NOVO NORDISK' },
];
function setStatus(state, text) {
const dot = document.getElementById('priceDot');
const label = document.getElementById('priceStatusText');
dot.className = 'price-dot ' + state;
label.textContent = text;
}
function flashPrice(id, newVal, oldVal) {
const el = document.getElementById('price-' + id);
if (!el) return;
const direction = newVal > oldVal ? 'flash-up' : newVal < oldVal ? 'flash-down' : '';
el.textContent = newVal.toLocaleString('da-DK', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
if (direction) {
el.classList.add(direction);
setTimeout(() => el.classList.remove(direction), 1200);
}
}
async function fetchHoldingPrices() {
setStatus('loading', 'fetching prices…');
// Read current displayed values before overwriting
const prev = {};
HOLDINGS.forEach(h => {
const el = document.getElementById('price-' + h.ticker);
if (el) prev[h.ticker] = parseFloat(el.textContent.replace(/\./g, '').replace(',', '.')) || 0;
});
try {
// POST to Go backend — expects JSON array: [{ticker, close}, ...]
const res = await fetch('/api/prices', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tickers: HOLDINGS.map(h => h.ticker) }),
});
if (!res.ok) throw new Error('backend returned ' + res.status);
const data = await res.json(); // [{ ticker, close, date, currency }, ...]
const map = {};
data.forEach(d => { map[d.ticker] = d.close; });
HOLDINGS.forEach(h => {
const close = map[h.ticker];
if (close != null) flashPrice(h.ticker, close, prev[h.ticker] || close);
});
const now = new Date().toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
setStatus('live', 'updated ' + now);
} catch (err) {
console.warn('Backend unavailable, trying Yahoo Finance directly…', err);
await fetchDirectYahoo(prev);
}
}
// Direct Yahoo Finance fallback (CORS proxy) — same approach as the React artifact
async function fetchDirectYahoo(prev) {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
// Roll back past weekends
while (yesterday.getDay() === 0 || yesterday.getDay() === 6) {
yesterday.setDate(yesterday.getDate() - 1);
}
const start = Math.floor(yesterday.setHours(0,0,0,0) / 1000);
const end = start + 86400;
let ok = 0, fail = 0;
await Promise.allSettled(HOLDINGS.map(async h => {
try {
const url = `https://corsproxy.io/?${encodeURIComponent(
`https://query1.finance.yahoo.com/v8/finance/chart/${h.ticker}?period1=${start}&period2=${end}&interval=1d`
)}`;
const res = await fetch(url);
const json = await res.json();
const close = json?.chart?.result?.[0]?.indicators?.quote?.[0]?.close?.[0];
if (close) {
flashPrice(h.ticker, close, prev[h.ticker] || close);
ok++;
} else {
fail++;
}
} catch { fail++; }
}));
const now = new Date().toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
if (fail === 0) {
setStatus('live', 'updated ' + now + ' (direct)');
} else if (ok > 0) {
setStatus('live', `${ok} ok, ${fail} failed — ${now}`);
} else {
setStatus('error', 'price fetch failed');
}
}
// Auto-fetch on page load
document.addEventListener('DOMContentLoaded', fetchHoldingPrices);
// ── Trades accordion ──
function toggleTrades(btn) {
const body = document.getElementById('tradesBody');
const expanded = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!expanded));
body.classList.toggle('open', !expanded);
}
function filterTrades(sym, btn) {
// Update active button
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const rows = document.querySelectorAll('#tradesBody_rows tr');
let visible = 0;
rows.forEach(row => {
const match = sym === 'all' || row.dataset.sym === sym;
row.classList.toggle('hidden-row', !match);
if (match) visible++;
});
document.getElementById('tradesCount').textContent =
visible + ' trade' + (visible !== 1 ? 's' : '') + (sym !== 'all' ? ' — ' + sym : '');
}
</script>
</body>
</html>
+451
View File
@@ -0,0 +1,451 @@
/* ─── Variables ─── */
:root {
--bg: #252525;
--surface: #2b2b2b;
--surface2: #313131;
--border: #3a3a3a;
--border-focus: #4e4e4e;
--text: #e8e8e0;
--muted: #777770;
--accent: #B88FE0;
--accent2: #6247AA;
--green: #6fcf8a;
--red: #e07070;
--yellow: #e0c870;
}
/* ─── Reset ─── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
/* ─── Base ─── */
body {
background: var(--bg);
color: var(--text);
font-family: 'DM Mono', monospace;
min-height: 100vh;
overflow-x: hidden;
}
/* Grain overlay */
body::before {
content: '';
position: fixed;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.04'/%3E%3C/svg%3E");
pointer-events: none;
z-index: 999;
opacity: 0.35;
}
/* ─── Layout ─── */
.container { max-width: 860px; margin: 0 auto; padding: 0 2rem; }
.container--wide { max-width: 1100px; margin: 0 auto; padding: 0 2rem; }
.container--narrow { max-width: 720px; margin: 0 auto; padding: 0 2rem; }
/* ─── Header ─── */
header {
padding: 4rem 0 3rem;
border-bottom: 1px solid var(--border);
animation: fadeUp 0.6s ease both;
}
.back {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.72rem;
color: var(--muted);
text-decoration: none;
letter-spacing: 0.1em;
text-transform: uppercase;
margin-bottom: 2rem;
transition: color 0.2s;
}
.back:hover { color: var(--accent); }
.tag {
font-size: 0.7rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 1rem;
}
h1 {
font-family: 'Syne', sans-serif;
font-size: clamp(2.2rem, 4vw, 3.5rem);
font-weight: 800;
line-height: 1.05;
letter-spacing: -0.02em;
}
h1 span { color: var(--accent); }
h1--hero { font-size: clamp(2.8rem, 6vw, 5rem); line-height: 1; }
.header-sub {
margin-top: 0.6rem;
font-size: 0.78rem;
color: var(--muted);
}
.byline {
margin-top: 1.2rem;
font-size: 0.72rem;
color: var(--muted);
display: flex;
gap: 1.5rem;
}
.byline span { color: var(--accent); }
/* ─── Typography ─── */
h2 {
font-family: 'Syne', sans-serif;
font-size: 1.2rem;
font-weight: 700;
color: var(--accent);
margin: 2.8rem 0 1rem;
letter-spacing: -0.01em;
}
h2::before {
content: '// ';
color: var(--accent2);
font-family: 'DM Mono', monospace;
font-weight: 300;
}
p {
font-size: 0.85rem;
line-height: 1.9;
color: #c8c8c0;
margin-bottom: 1.2rem;
}
code {
font-family: 'DM Mono', monospace;
font-size: 0.78rem;
color: var(--accent);
background: rgba(184, 143, 224, 0.08);
padding: 0.1rem 0.4rem;
border: 1px solid rgba(184, 143, 224, 0.15);
}
hr { border: none; border-top: 1px solid var(--border); margin: 2.5rem 0; }
.section-label {
font-size: 0.65rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 1.2rem;
}
.lead {
font-size: 1rem;
line-height: 1.9;
color: var(--text);
border-left: 2px solid var(--accent2);
padding-left: 1.5rem;
margin-bottom: 3rem;
}
blockquote {
margin: 2.5rem 0;
padding: 1.6rem 2rem;
background: var(--surface);
border-left: 3px solid var(--accent);
font-family: 'Syne', sans-serif;
font-size: 1.05rem;
font-weight: 700;
color: var(--text);
line-height: 1.5;
letter-spacing: -0.01em;
}
blockquote span { color: var(--accent); }
/* ─── Color utilities ─── */
.up { color: var(--green); }
.down { color: var(--red); }
.neutral { color: var(--muted); }
/* ─── Cards ─── */
.cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
margin: 3rem 0 2.5rem;
animation: fadeUp 0.6s 0.1s ease both;
}
.card { background: var(--surface); padding: 1.4rem 1.6rem; }
.card-label {
font-size: 0.62rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 0.5rem;
}
.card-value {
font-family: 'Syne', sans-serif;
font-size: 1.5rem;
font-weight: 700;
}
/* ─── Nav grid (homepage) ─── */
.nav-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
}
.nav-item {
display: block;
background: var(--surface);
padding: 2rem 2rem 1.8rem;
text-decoration: none;
color: var(--text);
position: relative;
overflow: hidden;
transition: background 0.2s ease;
animation: fadeUp 0.7s ease both;
}
.nav-item::after {
content: '';
position: absolute;
bottom: 0; left: 0;
width: 0; height: 2px;
background: var(--accent);
transition: width 0.3s ease;
}
.nav-item:hover { background: #161616; }
.nav-item:hover::after { width: 100%; }
.nav-item:hover .arrow { transform: translate(4px, -4px); }
.nav-number { font-size: 0.65rem; color: var(--muted); letter-spacing: 0.15em; margin-bottom: 1.4rem; }
.nav-title {
font-family: 'Syne', sans-serif;
font-size: 1.35rem;
font-weight: 700;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
}
.arrow { font-size: 1rem; color: var(--accent); transition: transform 0.25s ease; flex-shrink: 0; }
.nav-desc { font-size: 0.75rem; color: var(--muted); line-height: 1.6; }
/* ─── Table ─── */
.table-wrap { overflow-x: auto; animation: fadeUp 0.6s 0.2s ease both; margin-bottom: 2.5rem; }
table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }
thead tr { border-bottom: 1px solid var(--border); }
thead th {
font-size: 0.62rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
padding: 0.7rem 1rem;
text-align: right;
white-space: nowrap;
}
thead th:first-child { text-align: left; }
tbody tr { border-bottom: 1px solid var(--border); transition: background 0.15s; }
tbody tr:hover { background: var(--surface2); }
tbody td { padding: 1rem; text-align: right; white-space: nowrap; color: var(--text); }
tbody td:first-child { text-align: left; }
.ticker { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 0.9rem; color: var(--text); }
.currency-badge {
display: inline-block;
font-size: 0.6rem;
padding: 0.15rem 0.4rem;
border: 1px solid var(--border);
color: var(--muted);
margin-left: 0.4rem;
vertical-align: middle;
}
.weight-cell { display: flex; align-items: center; justify-content: flex-end; gap: 0.6rem; }
.weight-bar-track { width: 60px; height: 3px; background: var(--border); border-radius: 2px; overflow: hidden; }
.weight-bar-fill { height: 100%; background: linear-gradient(90deg, var(--accent2), var(--accent)); border-radius: 2px; }
.watchlist-row td { color: var(--muted); }
.watchlist-label { font-size: 0.62rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); padding: 1.2rem 1rem 0.5rem; }
/* ─── Skill bars ─── */
.skills-list { display: flex; flex-direction: column; gap: 0.75rem; }
.skill-row { display: grid; grid-template-columns: 80px 1fr; align-items: center; gap: 1rem; font-size: 0.78rem; color: var(--muted); }
.skill-bar-track { height: 3px; background: var(--border); border-radius: 2px; overflow: hidden; }
.skill-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent2), var(--accent));
border-radius: 2px;
transform-origin: left;
animation: growBar 0.8s ease both;
}
/* ─── Compare grid ─── */
.compare { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--border); border: 1px solid var(--border); margin: 2rem 0; }
.compare-col { background: var(--surface); padding: 1.4rem 1.6rem; }
.compare-col h3 { font-family: 'Syne', sans-serif; font-size: 0.85rem; font-weight: 700; margin-bottom: 1rem; padding-bottom: 0.6rem; border-bottom: 1px solid var(--border); }
.compare-col.bad h3 { color: var(--red); }
.compare-col.good h3 { color: var(--green); }
.compare-col ul { list-style: none; display: flex; flex-direction: column; gap: 0.6rem; }
.compare-col ul li { font-size: 0.75rem; color: var(--muted); line-height: 1.5; padding-left: 1rem; position: relative; }
.compare-col.bad ul li::before { content: '—'; position: absolute; left: 0; color: var(--red); }
.compare-col.good ul li::before { content: '+'; position: absolute; left: 0; color: var(--green); }
/* ─── Experience & Education ─── */
.exp-block { background: var(--surface); border: 1px solid var(--border); padding: 1.5rem; margin-bottom: 1px; }
.exp-block p { font-size: 0.78rem; color: var(--muted); line-height: 1.7; margin-top: 0.4rem; }
.edu-item { display: grid; grid-template-columns: 80px 1fr; gap: 1.5rem; margin-bottom: 1.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--border); }
.edu-item:last-child { border-bottom: none; margin-bottom: 0; }
.edu-year { font-size: 0.65rem; color: var(--accent); letter-spacing: 0.1em; padding-top: 0.15rem; }
.edu-title { font-family: 'Syne', sans-serif; font-size: 0.95rem; font-weight: 700; margin-bottom: 0.25rem; }
.edu-sub { font-size: 0.75rem; color: var(--muted); line-height: 1.6; }
.tags { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.tag-pill { font-size: 0.7rem; padding: 0.3rem 0.75rem; border: 1px solid var(--border); color: var(--muted); letter-spacing: 0.05em; }
/* ─── Forms / Login ─── */
.form-group { margin-bottom: 1.2rem; }
label { display: block; font-size: 0.62rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); margin-bottom: 0.5rem; }
input[type="text"], input[type="password"] {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
font-family: 'DM Mono', monospace;
font-size: 0.82rem;
padding: 0.7rem 0.9rem;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
-webkit-appearance: none;
}
input[type="text"]:focus, input[type="password"]:focus {
border-color: var(--accent2);
box-shadow: 0 0 0 2px rgba(98, 71, 170, 0.15);
}
input::placeholder { color: var(--muted); }
.btn { width: 100%; background: var(--accent2); color: var(--text); font-family: 'DM Mono', monospace; font-size: 0.78rem; letter-spacing: 0.1em; text-transform: uppercase; padding: 0.8rem 1rem; border: none; cursor: pointer; transition: background 0.2s, opacity 0.2s; }
.btn:hover { background: var(--accent); }
.btn:active { opacity: 0.8; }
/* ─── Footer ─── */
footer {
padding: 2.5rem 0 3rem;
border-top: 1px solid var(--border);
display: flex;
flex-direction: column;
align-items: center;
gap: 0.6rem;
text-align: center;
animation: fadeUp 0.6s 0.4s ease both;
}
footer a { color: var(--accent); text-decoration: none; font-size: 0.85rem; transition: opacity 0.2s; }
footer a:hover { opacity: 0.7; }
.footer-copy { font-size: 0.68rem; color: var(--muted); letter-spacing: 0.05em; }
/* ─── Animations ─── */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes growBar {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-6px); }
40% { transform: translateX(6px); }
60% { transform: translateX(-4px); }
80% { transform: translateX(4px); }
}
.shake { animation: shake 0.4s ease; }
/* ─── Article layout ─── */
article { padding: 3.5rem 0; animation: fadeUp 0.6s 0.1s ease both; }
main { padding: 3.5rem 0; }
/* ─── Classify grid (cyber page) ─── */
.classify-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
margin: 2rem 0;
}
.classify-card { background: var(--surface); padding: 1.4rem 1.3rem; }
.classify-card h3 { font-family: 'Syne', sans-serif; font-size: 0.9rem; font-weight: 700; margin-bottom: 0.5rem; }
.classify-card p { font-size: 0.72rem; color: var(--muted); margin: 0; line-height: 1.6; }
.level { font-size: 0.6rem; letter-spacing: 0.18em; text-transform: uppercase; margin-bottom: 0.6rem; font-weight: 500; }
.level-critical { color: var(--red); }
.level-high { color: var(--yellow); }
.level-standard { color: var(--green); }
/* ─── Principles (cyber page) ─── */
.principles { display: flex; flex-direction: column; gap: 1px; margin: 2rem 0; }
.principle { display: grid; grid-template-columns: 2.5rem 1fr; gap: 1.2rem; background: var(--surface); padding: 1.2rem 1.4rem; border: 1px solid var(--border); margin-bottom: 1px; align-items: start; }
.principle-num { font-family: 'Syne', sans-serif; font-size: 1.1rem; font-weight: 800; color: var(--accent2); padding-top: 0.1rem; }
.principle-title { font-family: 'Syne', sans-serif; font-size: 0.9rem; font-weight: 700; color: var(--text); margin-bottom: 0.35rem; }
.principle p { font-size: 0.77rem; color: var(--muted); line-height: 1.65; margin: 0; }
/* ─── Attack table (cyber page) ─── */
.attack-table { width: 100%; border-collapse: collapse; font-size: 0.76rem; margin: 2rem 0; }
.attack-table thead th { font-size: 0.6rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); padding: 0.6rem 1rem; text-align: left; border-bottom: 1px solid var(--border); }
.attack-table tbody tr { border-bottom: 1px solid var(--border); transition: background 0.15s; }
.attack-table tbody tr:hover { background: var(--surface2); }
.attack-table tbody td { padding: 0.9rem 1rem; color: #c8c8c0; vertical-align: top; line-height: 1.55; text-align: left; }
.attack-table td:first-child { color: var(--red); font-weight: 500; white-space: nowrap; }
.counter { color: var(--green); font-size: 0.72rem; }
/* ─── Login panel ─── */
.login-panel { border: 1px solid var(--border); background: var(--surface); padding: 2rem; }
.login-panel h2 { font-family: 'Syne', sans-serif; font-size: 1rem; font-weight: 700; margin-bottom: 0.3rem; }
.login-panel .desc { font-size: 0.72rem; color: var(--muted); margin-bottom: 2rem; line-height: 1.6; }
.twofa-group { display: none; margin-bottom: 1.2rem; }
.twofa-group.visible { display: block; }
.twofa-hint { font-size: 0.68rem; color: var(--muted); margin-top: 0.4rem; }
.btn-login { width: 100%; background: var(--accent2); color: var(--text); font-family: 'DM Mono', monospace; font-size: 0.78rem; letter-spacing: 0.1em; text-transform: uppercase; padding: 0.8rem 1rem; border: none; cursor: pointer; transition: background 0.2s, opacity 0.2s; margin-top: 0.4rem; }
.btn-login:hover { background: var(--accent); }
.btn-login:active { opacity: 0.8; }
.login-error { font-size: 0.72rem; color: var(--red); margin-top: 1rem; display: none; }
.login-error.visible { display: block; }
/* ─── Private dashboard ─── */
#private-dashboard { display: none; }
#private-dashboard.visible { display: block; }
.repo-list { display: flex; flex-direction: column; gap: 1px; }
.repo-item { background: var(--surface); border: 1px solid var(--border); padding: 1.1rem 1.3rem; display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; transition: background 0.15s; cursor: pointer; text-decoration: none; }
.repo-item:hover { background: var(--surface2); }
.repo-name { font-family: 'Syne', sans-serif; font-size: 0.88rem; font-weight: 700; color: var(--text); margin-bottom: 0.2rem; }
.repo-desc { font-size: 0.7rem; color: var(--muted); }
.repo-badge { font-size: 0.6rem; padding: 0.2rem 0.5rem; border: 1px solid var(--border); color: var(--muted); white-space: nowrap; flex-shrink: 0; }
.repo-badge.private { color: var(--red); border-color: rgba(224, 112, 112, 0.3); }
.repo-badge.active { color: var(--green); border-color: rgba(111, 207, 138, 0.3); }
.logout-btn { margin-top: 1.2rem; font-size: 0.65rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); background: none; border: none; cursor: pointer; font-family: 'DM Mono', monospace; padding: 0; transition: color 0.2s; }
.logout-btn:hover { color: var(--red); }
/* ─── Empty state ─── */
.empty-state { border: 1px dashed var(--border); padding: 3rem 2rem; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 0.8rem; min-height: 280px; }
.empty-icon { font-size: 1.8rem; opacity: 0.2; }
.empty-title { font-family: 'Syne', sans-serif; font-size: 0.9rem; font-weight: 700; color: var(--muted); }
.empty-sub { font-size: 0.7rem; color: var(--muted); opacity: 0.6; max-width: 200px; line-height: 1.6; }
/* ─── Two column main ─── */
main.two-col { padding: 3.5rem 0; display: grid; grid-template-columns: 1fr 1fr; gap: 3rem; animation: fadeUp 0.6s 0.1s ease both; }
/* ─── Responsive ─── */
@media (max-width: 700px) { .cards { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px) { main.two-col { grid-template-columns: 1fr; gap: 2.5rem; } }
@media (max-width: 560px) {
.nav-grid { grid-template-columns: 1fr; }
.edu-item { grid-template-columns: 1fr; gap: 0.4rem; }
.skill-row { grid-template-columns: 70px 1fr; }
.compare { grid-template-columns: 1fr; }
footer { flex-direction: column; gap: 0.5rem; }
}
@media (max-width: 420px) { .cards { grid-template-columns: 1fr; } }